Linux-2.6.12-rc2

Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.

Let it rip!
diff --git a/net/decnet/Kconfig b/net/decnet/Kconfig
new file mode 100644
index 0000000..2101da5
--- /dev/null
+++ b/net/decnet/Kconfig
@@ -0,0 +1,27 @@
+#
+# DECnet configuration
+#
+config DECNET_ROUTER
+	bool "DECnet: router support (EXPERIMENTAL)"
+	depends on DECNET && EXPERIMENTAL
+	---help---
+	  Add support for turning your DECnet Endnode into a level 1 or 2
+	  router.  This is an experimental, but functional option.  If you
+	  do say Y here, then make sure that you also say Y to "Kernel/User
+	  network link driver", "Routing messages" and "Network packet
+	  filtering".  The first two are required to allow configuration via
+	  rtnetlink (you will need Alexey Kuznetsov's iproute2 package
+	  from <ftp://ftp.tux.org/pub/net/ip-routing/>). The "Network packet
+	  filtering" option will be required for the forthcoming routing daemon
+	  to work.
+
+	  See <file:Documentation/networking/decnet.txt> for more information.
+
+config DECNET_ROUTE_FWMARK
+	bool "DECnet: use FWMARK value as routing key (EXPERIMENTAL)"
+	depends on DECNET_ROUTER && NETFILTER
+	help
+	  If you say Y here, you will be able to specify different routes for
+	  packets with different FWMARK ("firewalling mark") values
+	  (see ipchains(8), "-m" argument).
+
diff --git a/net/decnet/Makefile b/net/decnet/Makefile
new file mode 100644
index 0000000..e44003a
--- /dev/null
+++ b/net/decnet/Makefile
@@ -0,0 +1,10 @@
+
+obj-$(CONFIG_DECNET) += decnet.o
+
+decnet-y := af_decnet.o dn_nsp_in.o dn_nsp_out.o \
+	    dn_route.o dn_dev.o dn_neigh.o dn_timer.o
+decnet-$(CONFIG_DECNET_ROUTER) += dn_fib.o dn_rules.o dn_table.o
+decnet-y += sysctl_net_decnet.o
+
+obj-$(CONFIG_NETFILTER) += netfilter/
+
diff --git a/net/decnet/README b/net/decnet/README
new file mode 100644
index 0000000..60e7ec8
--- /dev/null
+++ b/net/decnet/README
@@ -0,0 +1,8 @@
+                       Linux DECnet Project
+                      ======================
+
+The documentation for this kernel subsystem is available in the
+Documentation/networking subdirectory of this distribution and also
+on line at http://www.chygwyn.com/DECnet/
+
+Steve Whitehouse <SteveW@ACM.org>
diff --git a/net/decnet/TODO b/net/decnet/TODO
new file mode 100644
index 0000000..ebb5ac6
--- /dev/null
+++ b/net/decnet/TODO
@@ -0,0 +1,41 @@
+Steve's quick list of things that need finishing off:
+[they are in no particular order and range from the trivial to the long winded]
+
+ o Proper timeouts on each neighbour (in routing mode) rather than
+   just the 60 second On-Ethernet cache value.
+
+ o Support for X.25 linklayer
+
+ o Support for DDCMP link layer
+
+ o The DDCMP device itself
+
+ o PPP support (rfc1762)
+
+ o Lots of testing with real applications
+
+ o Verify errors etc. against POSIX 1003.1g (draft)
+
+ o Using send/recvmsg() to get at connect/disconnect data (POSIX 1003.1g) 
+   [maybe this should be done at socket level... the control data in the
+    send/recvmsg() calls should simply be a vector of set/getsockopt()
+    calls]
+
+ o check MSG_CTRUNC is set where it should be.
+
+ o Find all the commonality between DECnet and IPv4 routing code and extract 
+   it into a small library of routines. [probably a project for 2.7.xx]
+
+ o Add perfect socket hashing - an idea suggested by Paul Koning. Currently
+   we have a half-way house scheme which seems to work reasonably well, but
+   the full scheme is still worth implementing, its not not top of my list
+   right now.
+
+ o Add session control message flow control
+
+ o Add NSP message flow control
+
+ o DECnet sendpages() function
+
+ o AIO for DECnet
+
diff --git a/net/decnet/af_decnet.c b/net/decnet/af_decnet.c
new file mode 100644
index 0000000..29bb3cd
--- /dev/null
+++ b/net/decnet/af_decnet.c
@@ -0,0 +1,2405 @@
+
+/*
+ * DECnet       An implementation of the DECnet protocol suite for the LINUX
+ *              operating system.  DECnet is implemented using the  BSD Socket
+ *              interface as the means of communication with the user level.
+ *
+ *              DECnet Socket Layer Interface
+ *
+ * Authors:     Eduardo Marcelo Serrat <emserrat@geocities.com>
+ *              Patrick Caulfield <patrick@pandh.demon.co.uk>
+ *
+ * Changes:
+ *        Steve Whitehouse: Copied from Eduardo Serrat and Patrick Caulfield's
+ *                          version of the code. Original copyright preserved
+ *                          below.
+ *        Steve Whitehouse: Some bug fixes, cleaning up some code to make it
+ *                          compatible with my routing layer.
+ *        Steve Whitehouse: Merging changes from Eduardo Serrat and Patrick
+ *                          Caulfield.
+ *        Steve Whitehouse: Further bug fixes, checking module code still works
+ *                          with new routing layer.
+ *        Steve Whitehouse: Additional set/get_sockopt() calls.
+ *        Steve Whitehouse: Fixed TIOCINQ ioctl to be same as Eduardo's new
+ *                          code.
+ *        Steve Whitehouse: recvmsg() changed to try and behave in a POSIX like
+ *                          way. Didn't manage it entirely, but its better.
+ *        Steve Whitehouse: ditto for sendmsg().
+ *        Steve Whitehouse: A selection of bug fixes to various things.
+ *        Steve Whitehouse: Added TIOCOUTQ ioctl.
+ *        Steve Whitehouse: Fixes to username2sockaddr & sockaddr2username.
+ *        Steve Whitehouse: Fixes to connect() error returns.
+ *       Patrick Caulfield: Fixes to delayed acceptance logic.
+ *         David S. Miller: New socket locking
+ *        Steve Whitehouse: Socket list hashing/locking
+ *         Arnaldo C. Melo: use capable, not suser
+ *        Steve Whitehouse: Removed unused code. Fix to use sk->allocation
+ *                          when required.
+ *       Patrick Caulfield: /proc/net/decnet now has object name/number
+ *        Steve Whitehouse: Fixed local port allocation, hashed sk list
+ *          Matthew Wilcox: Fixes for dn_ioctl()
+ *        Steve Whitehouse: New connect/accept logic to allow timeouts and
+ *                          prepare for sendpage etc.
+ */
+
+
+/******************************************************************************
+    (c) 1995-1998 E.M. Serrat		emserrat@geocities.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
+    any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+HISTORY:
+
+Version           Kernel     Date       Author/Comments
+-------           ------     ----       ---------------
+Version 0.0.1     2.0.30    01-dic-97	Eduardo Marcelo Serrat
+					(emserrat@geocities.com)
+
+                                        First Development of DECnet Socket La-
+					yer for Linux. Only supports outgoing
+					connections.
+
+Version 0.0.2	  2.1.105   20-jun-98   Patrick J. Caulfield
+					(patrick@pandh.demon.co.uk)
+
+					Port to new kernel development version.
+
+Version 0.0.3     2.1.106   25-jun-98   Eduardo Marcelo Serrat
+					(emserrat@geocities.com)
+					_
+                                        Added support for incoming connections
+                                        so we can start developing server apps
+                                        on Linux.
+					-
+					Module Support
+Version 0.0.4     2.1.109   21-jul-98   Eduardo Marcelo Serrat
+                                       (emserrat@geocities.com)
+                                       _
+                                        Added support for X11R6.4. Now we can 
+                                        use DECnet transport for X on Linux!!!
+                                       -
+Version 0.0.5    2.1.110   01-aug-98   Eduardo Marcelo Serrat
+                                       (emserrat@geocities.com)
+                                       Removed bugs on flow control
+                                       Removed bugs on incoming accessdata
+                                       order
+                                       -
+Version 0.0.6    2.1.110   07-aug-98   Eduardo Marcelo Serrat
+                                       dn_recvmsg fixes
+
+                                        Patrick J. Caulfield
+                                       dn_bind fixes
+*******************************************************************************/
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <linux/netdevice.h>
+#include <linux/inet.h>
+#include <linux/route.h>
+#include <linux/netfilter.h>
+#include <linux/seq_file.h>
+#include <net/sock.h>
+#include <net/tcp.h>
+#include <net/flow.h>
+#include <asm/system.h>
+#include <asm/ioctls.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/proc_fs.h>
+#include <linux/stat.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <net/neighbour.h>
+#include <net/dst.h>
+#include <net/dn.h>
+#include <net/dn_nsp.h>
+#include <net/dn_dev.h>
+#include <net/dn_route.h>
+#include <net/dn_fib.h>
+#include <net/dn_neigh.h>
+
+struct dn_sock {
+	struct sock sk;
+	struct dn_scp scp;
+};
+
+static void dn_keepalive(struct sock *sk);
+
+#define DN_SK_HASH_SHIFT 8
+#define DN_SK_HASH_SIZE (1 << DN_SK_HASH_SHIFT)
+#define DN_SK_HASH_MASK (DN_SK_HASH_SIZE - 1)
+
+
+static struct proto_ops dn_proto_ops;
+static DEFINE_RWLOCK(dn_hash_lock);
+static struct hlist_head dn_sk_hash[DN_SK_HASH_SIZE];
+static struct hlist_head dn_wild_sk;
+
+static int __dn_setsockopt(struct socket *sock, int level, int optname, char __user *optval, int optlen, int flags);
+static int __dn_getsockopt(struct socket *sock, int level, int optname, char __user *optval, int __user *optlen, int flags);
+
+static struct hlist_head *dn_find_list(struct sock *sk)
+{
+	struct dn_scp *scp = DN_SK(sk);
+
+	if (scp->addr.sdn_flags & SDF_WILD)
+		return hlist_empty(&dn_wild_sk) ? &dn_wild_sk : NULL;
+
+	return &dn_sk_hash[scp->addrloc & DN_SK_HASH_MASK];
+}
+
+/* 
+ * Valid ports are those greater than zero and not already in use.
+ */
+static int check_port(unsigned short port)
+{
+	struct sock *sk;
+	struct hlist_node *node;
+
+	if (port == 0)
+		return -1;
+
+	sk_for_each(sk, node, &dn_sk_hash[port & DN_SK_HASH_MASK]) {
+		struct dn_scp *scp = DN_SK(sk);
+		if (scp->addrloc == port)
+			return -1;
+	}
+	return 0;
+}
+
+static unsigned short port_alloc(struct sock *sk)
+{
+	struct dn_scp *scp = DN_SK(sk);
+static unsigned short port = 0x2000;
+	unsigned short i_port = port;
+
+	while(check_port(++port) != 0) {
+		if (port == i_port)
+			return 0;
+	}
+
+	scp->addrloc = port;
+
+	return 1;
+}
+
+/*
+ * Since this is only ever called from user
+ * level, we don't need a write_lock() version
+ * of this.
+ */
+static int dn_hash_sock(struct sock *sk)
+{
+	struct dn_scp *scp = DN_SK(sk);
+	struct hlist_head *list;
+	int rv = -EUSERS;
+
+	BUG_ON(sk_hashed(sk));
+
+	write_lock_bh(&dn_hash_lock);
+	
+	if (!scp->addrloc && !port_alloc(sk))
+		goto out;
+
+	rv = -EADDRINUSE;
+	if ((list = dn_find_list(sk)) == NULL)
+		goto out;
+
+	sk_add_node(sk, list);
+	rv = 0;
+out:
+	write_unlock_bh(&dn_hash_lock);
+	return rv;
+}
+
+static void dn_unhash_sock(struct sock *sk)
+{
+	write_lock(&dn_hash_lock);
+	sk_del_node_init(sk);
+	write_unlock(&dn_hash_lock);
+}
+
+static void dn_unhash_sock_bh(struct sock *sk)
+{
+	write_lock_bh(&dn_hash_lock);
+	sk_del_node_init(sk);
+	write_unlock_bh(&dn_hash_lock);
+}
+
+static struct hlist_head *listen_hash(struct sockaddr_dn *addr)
+{
+	int i;
+	unsigned hash = addr->sdn_objnum;
+
+	if (hash == 0) {
+		hash = addr->sdn_objnamel;
+		for(i = 0; i < dn_ntohs(addr->sdn_objnamel); i++) {
+			hash ^= addr->sdn_objname[i];
+			hash ^= (hash << 3);
+		}
+	}
+
+	return &dn_sk_hash[hash & DN_SK_HASH_MASK];
+}
+
+/*
+ * Called to transform a socket from bound (i.e. with a local address)
+ * into a listening socket (doesn't need a local port number) and rehashes
+ * based upon the object name/number.
+ */
+static void dn_rehash_sock(struct sock *sk)
+{
+	struct hlist_head *list;
+	struct dn_scp *scp = DN_SK(sk);
+
+	if (scp->addr.sdn_flags & SDF_WILD)
+		return;
+
+	write_lock_bh(&dn_hash_lock);
+	sk_del_node_init(sk);
+	DN_SK(sk)->addrloc = 0;
+	list = listen_hash(&DN_SK(sk)->addr);
+	sk_add_node(sk, list);
+	write_unlock_bh(&dn_hash_lock);
+}
+
+int dn_sockaddr2username(struct sockaddr_dn *sdn, unsigned char *buf, unsigned char type)
+{
+	int len = 2;
+
+	*buf++ = type;
+
+	switch(type) {
+		case 0:
+			*buf++ = sdn->sdn_objnum;
+			break;
+		case 1:
+			*buf++ = 0;
+			*buf++ = dn_ntohs(sdn->sdn_objnamel);
+			memcpy(buf, sdn->sdn_objname, dn_ntohs(sdn->sdn_objnamel));
+			len = 3 + dn_ntohs(sdn->sdn_objnamel);
+			break;
+		case 2:
+			memset(buf, 0, 5);
+			buf += 5;
+			*buf++ = dn_ntohs(sdn->sdn_objnamel);
+			memcpy(buf, sdn->sdn_objname, dn_ntohs(sdn->sdn_objnamel));
+			len = 7 + dn_ntohs(sdn->sdn_objnamel);
+			break;
+	}
+
+	return len;
+}
+
+/*
+ * On reception of usernames, we handle types 1 and 0 for destination
+ * addresses only. Types 2 and 4 are used for source addresses, but the
+ * UIC, GIC are ignored and they are both treated the same way. Type 3
+ * is never used as I've no idea what its purpose might be or what its
+ * format is.
+ */
+int dn_username2sockaddr(unsigned char *data, int len, struct sockaddr_dn *sdn, unsigned char *fmt)
+{
+	unsigned char type;
+	int size = len;
+	int namel = 12;
+
+	sdn->sdn_objnum = 0;
+	sdn->sdn_objnamel = dn_htons(0);
+	memset(sdn->sdn_objname, 0, DN_MAXOBJL);
+
+	if (len < 2)
+		return -1;
+
+	len -= 2;
+	*fmt = *data++;
+	type = *data++;
+
+	switch(*fmt) {
+		case 0:
+			sdn->sdn_objnum = type;
+			return 2;
+		case 1:
+			namel = 16;
+			break;
+		case 2:
+			len  -= 4;
+			data += 4;
+			break;
+		case 4:
+			len  -= 8;
+			data += 8;
+			break;
+		default:
+			return -1;
+	}
+
+	len -= 1;
+
+	if (len < 0)
+		return -1;
+
+	sdn->sdn_objnamel = dn_htons(*data++);
+	len -= dn_ntohs(sdn->sdn_objnamel);
+
+	if ((len < 0) || (dn_ntohs(sdn->sdn_objnamel) > namel))
+		return -1;
+
+	memcpy(sdn->sdn_objname, data, dn_ntohs(sdn->sdn_objnamel));
+
+	return size - len;
+}
+
+struct sock *dn_sklist_find_listener(struct sockaddr_dn *addr)
+{
+	struct hlist_head *list = listen_hash(addr);
+	struct hlist_node *node;
+	struct sock *sk;
+
+	read_lock(&dn_hash_lock);
+	sk_for_each(sk, node, list) {
+		struct dn_scp *scp = DN_SK(sk);
+		if (sk->sk_state != TCP_LISTEN)
+			continue;
+		if (scp->addr.sdn_objnum) {
+			if (scp->addr.sdn_objnum != addr->sdn_objnum)
+				continue;
+		} else {
+			if (addr->sdn_objnum)
+				continue;
+			if (scp->addr.sdn_objnamel != addr->sdn_objnamel)
+				continue;
+			if (memcmp(scp->addr.sdn_objname, addr->sdn_objname, dn_ntohs(addr->sdn_objnamel)) != 0)
+				continue;
+		}
+		sock_hold(sk);
+		read_unlock(&dn_hash_lock);
+		return sk;
+	}
+
+	sk = sk_head(&dn_wild_sk);
+	if (sk) {
+	       	if (sk->sk_state == TCP_LISTEN)
+			sock_hold(sk);
+		else
+			sk = NULL;
+	}
+
+	read_unlock(&dn_hash_lock);
+	return sk;
+}
+
+struct sock *dn_find_by_skb(struct sk_buff *skb)
+{
+	struct dn_skb_cb *cb = DN_SKB_CB(skb);
+	struct sock *sk;
+	struct hlist_node *node;
+	struct dn_scp *scp;
+
+	read_lock(&dn_hash_lock);
+	sk_for_each(sk, node, &dn_sk_hash[cb->dst_port & DN_SK_HASH_MASK]) {
+		scp = DN_SK(sk);
+		if (cb->src != dn_saddr2dn(&scp->peer))
+			continue;
+		if (cb->dst_port != scp->addrloc)
+			continue;
+		if (scp->addrrem && (cb->src_port != scp->addrrem))
+			continue;
+		sock_hold(sk);
+		goto found;
+	}
+	sk = NULL;
+found:
+	read_unlock(&dn_hash_lock);
+	return sk;
+}
+
+
+
+static void dn_destruct(struct sock *sk)
+{
+	struct dn_scp *scp = DN_SK(sk);
+
+	skb_queue_purge(&scp->data_xmit_queue);
+	skb_queue_purge(&scp->other_xmit_queue);
+	skb_queue_purge(&scp->other_receive_queue);
+
+	dst_release(xchg(&sk->sk_dst_cache, NULL));
+}
+
+static struct proto dn_proto = {
+	.name	  = "DECNET",
+	.owner	  = THIS_MODULE,
+	.obj_size = sizeof(struct dn_sock),
+};
+
+static struct sock *dn_alloc_sock(struct socket *sock, int gfp)
+{
+	struct dn_scp *scp;
+	struct sock *sk = sk_alloc(PF_DECnet, gfp, &dn_proto, 1);
+
+	if  (!sk)
+		goto out;
+
+	if (sock)
+		sock->ops = &dn_proto_ops;
+	sock_init_data(sock, sk);
+
+	sk->sk_backlog_rcv = dn_nsp_backlog_rcv;
+	sk->sk_destruct    = dn_destruct;
+	sk->sk_no_check    = 1;
+	sk->sk_family      = PF_DECnet;
+	sk->sk_protocol    = 0;
+	sk->sk_allocation  = gfp;
+
+	/* Initialization of DECnet Session Control Port		*/
+	scp = DN_SK(sk);
+	scp->state	= DN_O;		/* Open			*/
+	scp->numdat	= 1;		/* Next data seg to tx	*/
+	scp->numoth	= 1;		/* Next oth data to tx  */
+	scp->ackxmt_dat = 0;		/* Last data seg ack'ed */
+	scp->ackxmt_oth = 0;		/* Last oth data ack'ed */
+	scp->ackrcv_dat = 0;		/* Highest data ack recv*/
+	scp->ackrcv_oth = 0;		/* Last oth data ack rec*/
+        scp->flowrem_sw = DN_SEND;
+	scp->flowloc_sw = DN_SEND;
+	scp->flowrem_dat = 0;
+	scp->flowrem_oth = 1;
+	scp->flowloc_dat = 0;
+	scp->flowloc_oth = 1;
+	scp->services_rem = 0;
+	scp->services_loc = 1 | NSP_FC_NONE;
+	scp->info_rem = 0;
+	scp->info_loc = 0x03; /* NSP version 4.1 */
+	scp->segsize_rem = 230 - DN_MAX_NSP_DATA_HEADER; /* Default: Updated by remote segsize */
+	scp->nonagle = 0;
+	scp->multi_ireq = 1;
+	scp->accept_mode = ACC_IMMED;
+	scp->addr.sdn_family    = AF_DECnet;
+	scp->peer.sdn_family    = AF_DECnet;
+	scp->accessdata.acc_accl = 5;
+	memcpy(scp->accessdata.acc_acc, "LINUX", 5);
+
+	scp->max_window   = NSP_MAX_WINDOW;
+	scp->snd_window   = NSP_MIN_WINDOW;
+	scp->nsp_srtt     = NSP_INITIAL_SRTT;
+	scp->nsp_rttvar   = NSP_INITIAL_RTTVAR;
+	scp->nsp_rxtshift = 0;
+
+	skb_queue_head_init(&scp->data_xmit_queue);
+	skb_queue_head_init(&scp->other_xmit_queue);
+	skb_queue_head_init(&scp->other_receive_queue);
+
+	scp->persist = 0;
+	scp->persist_fxn = NULL;
+	scp->keepalive = 10 * HZ;
+	scp->keepalive_fxn = dn_keepalive;
+
+	init_timer(&scp->delack_timer);
+	scp->delack_pending = 0;
+	scp->delack_fxn = dn_nsp_delayed_ack;
+
+	dn_start_slow_timer(sk);
+out:
+	return sk;
+}
+
+/*
+ * Keepalive timer.
+ * FIXME: Should respond to SO_KEEPALIVE etc.
+ */
+static void dn_keepalive(struct sock *sk)
+{
+	struct dn_scp *scp = DN_SK(sk);
+
+	/*
+	 * By checking the other_data transmit queue is empty
+	 * we are double checking that we are not sending too
+	 * many of these keepalive frames.
+	 */
+	if (skb_queue_len(&scp->other_xmit_queue) == 0)
+		dn_nsp_send_link(sk, DN_NOCHANGE, 0);
+}
+
+
+/*
+ * Timer for shutdown/destroyed sockets.
+ * When socket is dead & no packets have been sent for a
+ * certain amount of time, they are removed by this
+ * routine. Also takes care of sending out DI & DC
+ * frames at correct times.
+ */
+int dn_destroy_timer(struct sock *sk)
+{
+	struct dn_scp *scp = DN_SK(sk);
+
+	scp->persist = dn_nsp_persist(sk);
+
+	switch(scp->state) {
+		case DN_DI:
+			dn_nsp_send_disc(sk, NSP_DISCINIT, 0, GFP_ATOMIC);
+			if (scp->nsp_rxtshift >= decnet_di_count)
+				scp->state = DN_CN;
+			return 0;
+
+		case DN_DR:
+			dn_nsp_send_disc(sk, NSP_DISCINIT, 0, GFP_ATOMIC);
+			if (scp->nsp_rxtshift >= decnet_dr_count)
+				scp->state = DN_DRC;
+			return 0;
+
+		case DN_DN:
+			if (scp->nsp_rxtshift < decnet_dn_count) {
+				/* printk(KERN_DEBUG "dn_destroy_timer: DN\n"); */
+				dn_nsp_send_disc(sk, NSP_DISCCONF, NSP_REASON_DC, GFP_ATOMIC);
+				return 0;
+			}
+	}
+
+	scp->persist = (HZ * decnet_time_wait);
+
+	if (sk->sk_socket)
+		return 0;
+
+	if ((jiffies - scp->stamp) >= (HZ * decnet_time_wait)) {
+		dn_unhash_sock(sk);
+		sock_put(sk);
+		return 1;
+	}
+
+	return 0;
+}
+
+static void dn_destroy_sock(struct sock *sk)
+{
+	struct dn_scp *scp = DN_SK(sk);
+
+	scp->nsp_rxtshift = 0; /* reset back off */
+
+	if (sk->sk_socket) {
+		if (sk->sk_socket->state != SS_UNCONNECTED)
+			sk->sk_socket->state = SS_DISCONNECTING;
+	}
+
+	sk->sk_state = TCP_CLOSE;
+
+	switch(scp->state) {
+		case DN_DN:
+			dn_nsp_send_disc(sk, NSP_DISCCONF, NSP_REASON_DC,
+					 sk->sk_allocation);
+			scp->persist_fxn = dn_destroy_timer;
+			scp->persist = dn_nsp_persist(sk);
+			break;
+		case DN_CR:
+			scp->state = DN_DR;
+			goto disc_reject;
+		case DN_RUN:
+			scp->state = DN_DI;
+		case DN_DI:
+		case DN_DR:
+disc_reject:
+			dn_nsp_send_disc(sk, NSP_DISCINIT, 0, sk->sk_allocation);
+		case DN_NC:
+		case DN_NR:
+		case DN_RJ:
+		case DN_DIC:
+		case DN_CN:
+		case DN_DRC:
+		case DN_CI:
+		case DN_CD:
+			scp->persist_fxn = dn_destroy_timer;
+			scp->persist = dn_nsp_persist(sk);
+			break;
+		default:
+			printk(KERN_DEBUG "DECnet: dn_destroy_sock passed socket in invalid state\n");
+		case DN_O:
+			dn_stop_slow_timer(sk);
+
+			dn_unhash_sock_bh(sk);
+			sock_put(sk);
+
+			break;
+	}
+}
+
+char *dn_addr2asc(dn_address addr, char *buf)
+{
+	unsigned short node, area;
+
+	node = addr & 0x03ff;
+	area = addr >> 10;
+	sprintf(buf, "%hd.%hd", area, node);
+
+	return buf;
+}
+
+
+
+static int dn_create(struct socket *sock, int protocol)
+{
+	struct sock *sk;
+
+	switch(sock->type) {
+		case SOCK_SEQPACKET:
+			if (protocol != DNPROTO_NSP)
+				return -EPROTONOSUPPORT;
+			break;
+		case SOCK_STREAM:
+			break;
+		default:
+			return -ESOCKTNOSUPPORT;
+	}
+
+
+	if ((sk = dn_alloc_sock(sock, GFP_KERNEL)) == NULL) 
+		return -ENOBUFS;
+
+	sk->sk_protocol = protocol;
+
+	return 0;
+}
+
+
+static int
+dn_release(struct socket *sock)
+{
+	struct sock *sk = sock->sk;
+
+	if (sk) {
+		sock_orphan(sk);
+		sock_hold(sk);
+		lock_sock(sk);
+		dn_destroy_sock(sk);
+		release_sock(sk);
+		sock_put(sk);
+	}
+
+        return 0;
+}
+
+static int dn_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
+{
+	struct sock *sk = sock->sk;
+	struct dn_scp *scp = DN_SK(sk);
+	struct sockaddr_dn *saddr = (struct sockaddr_dn *)uaddr;
+	struct net_device *dev;
+	int rv;
+
+	if (addr_len != sizeof(struct sockaddr_dn))
+		return -EINVAL;
+
+	if (saddr->sdn_family != AF_DECnet)
+		return -EINVAL;
+
+	if (dn_ntohs(saddr->sdn_nodeaddrl) && (dn_ntohs(saddr->sdn_nodeaddrl) != 2))
+		return -EINVAL;
+
+	if (dn_ntohs(saddr->sdn_objnamel) > DN_MAXOBJL)
+		return -EINVAL;
+
+	if (saddr->sdn_flags & ~SDF_WILD)
+		return -EINVAL;
+
+#if 1
+	if (!capable(CAP_NET_BIND_SERVICE) && (saddr->sdn_objnum ||
+	    (saddr->sdn_flags & SDF_WILD)))
+		return -EACCES;
+#else
+	/*
+	 * Maybe put the default actions in the default security ops for
+	 * dn_prot_sock ? Would be nice if the capable call would go there
+	 * too.
+	 */
+	if (security_dn_prot_sock(saddr) &&
+	    !capable(CAP_NET_BIND_SERVICE) || 
+	    saddr->sdn_objnum || (saddr->sdn_flags & SDF_WILD))
+		return -EACCES;
+#endif
+
+
+	if (!(saddr->sdn_flags & SDF_WILD)) {
+		if (dn_ntohs(saddr->sdn_nodeaddrl)) {
+			read_lock(&dev_base_lock);
+			for(dev = dev_base; dev; dev = dev->next) {
+				if (!dev->dn_ptr)
+					continue;
+				if (dn_dev_islocal(dev, dn_saddr2dn(saddr)))
+					break;
+			}
+			read_unlock(&dev_base_lock);
+			if (dev == NULL)
+				return -EADDRNOTAVAIL;
+		}
+	}
+
+	rv = -EINVAL;
+	lock_sock(sk);
+	if (sock_flag(sk, SOCK_ZAPPED)) {
+		memcpy(&scp->addr, saddr, addr_len);
+		sock_reset_flag(sk, SOCK_ZAPPED);
+
+		rv = dn_hash_sock(sk);
+		if (rv)
+			sock_set_flag(sk, SOCK_ZAPPED);
+	}
+	release_sock(sk);
+
+        return rv;
+}
+
+
+static int dn_auto_bind(struct socket *sock)
+{
+	struct sock *sk = sock->sk;
+	struct dn_scp *scp = DN_SK(sk);
+	int rv;
+
+	sock_reset_flag(sk, SOCK_ZAPPED);
+
+	scp->addr.sdn_flags  = 0;
+	scp->addr.sdn_objnum = 0;
+
+	/*
+	 * This stuff is to keep compatibility with Eduardo's
+	 * patch. I hope I can dispense with it shortly...
+	 */
+	if ((scp->accessdata.acc_accl != 0) &&
+		(scp->accessdata.acc_accl <= 12)) {
+	
+		scp->addr.sdn_objnamel = dn_htons(scp->accessdata.acc_accl);
+		memcpy(scp->addr.sdn_objname, scp->accessdata.acc_acc, dn_ntohs(scp->addr.sdn_objnamel));
+
+		scp->accessdata.acc_accl = 0;
+		memset(scp->accessdata.acc_acc, 0, 40);
+	}
+	/* End of compatibility stuff */
+
+	scp->addr.sdn_add.a_len = dn_htons(2);
+	rv = dn_dev_bind_default((dn_address *)scp->addr.sdn_add.a_addr);
+	if (rv == 0) {
+		rv = dn_hash_sock(sk);
+		if (rv)
+			sock_set_flag(sk, SOCK_ZAPPED);
+	}
+
+	return rv;
+}
+
+static int dn_confirm_accept(struct sock *sk, long *timeo, int allocation)
+{
+	struct dn_scp *scp = DN_SK(sk);
+	DEFINE_WAIT(wait);
+	int err;
+
+	if (scp->state != DN_CR)
+		return -EINVAL;
+
+	scp->state = DN_CC;
+	scp->segsize_loc = dst_metric(__sk_dst_get(sk), RTAX_ADVMSS);
+	dn_send_conn_conf(sk, allocation);
+
+	prepare_to_wait(sk->sk_sleep, &wait, TASK_INTERRUPTIBLE);
+	for(;;) {
+		release_sock(sk);
+		if (scp->state == DN_CC)
+			*timeo = schedule_timeout(*timeo);
+		lock_sock(sk);
+		err = 0;
+		if (scp->state == DN_RUN)
+			break;
+		err = sock_error(sk);
+		if (err)
+			break;
+		err = sock_intr_errno(*timeo);
+		if (signal_pending(current))
+			break;
+		err = -EAGAIN;
+		if (!*timeo)
+			break;
+		prepare_to_wait(sk->sk_sleep, &wait, TASK_INTERRUPTIBLE);
+	}
+	finish_wait(sk->sk_sleep, &wait);
+	if (err == 0) {
+		sk->sk_socket->state = SS_CONNECTED;
+	} else if (scp->state != DN_CC) {
+		sk->sk_socket->state = SS_UNCONNECTED;
+	}
+	return err;
+}
+
+static int dn_wait_run(struct sock *sk, long *timeo)
+{
+	struct dn_scp *scp = DN_SK(sk);
+	DEFINE_WAIT(wait);
+	int err = 0;
+
+	if (scp->state == DN_RUN)
+		goto out;
+
+	if (!*timeo)
+		return -EALREADY;
+
+	prepare_to_wait(sk->sk_sleep, &wait, TASK_INTERRUPTIBLE);
+	for(;;) {
+		release_sock(sk);
+		if (scp->state == DN_CI || scp->state == DN_CC)
+			*timeo = schedule_timeout(*timeo);
+		lock_sock(sk);
+		err = 0;
+		if (scp->state == DN_RUN)
+			break;
+		err = sock_error(sk);
+		if (err)
+			break;
+		err = sock_intr_errno(*timeo);
+		if (signal_pending(current))
+			break;
+		err = -ETIMEDOUT;
+		if (!*timeo)
+			break;
+		prepare_to_wait(sk->sk_sleep, &wait, TASK_INTERRUPTIBLE);
+	}
+	finish_wait(sk->sk_sleep, &wait);
+out:
+	if (err == 0) {
+		sk->sk_socket->state = SS_CONNECTED;
+	} else if (scp->state != DN_CI && scp->state != DN_CC) {
+		sk->sk_socket->state = SS_UNCONNECTED;
+	}
+	return err;
+}
+
+static int __dn_connect(struct sock *sk, struct sockaddr_dn *addr, int addrlen, long *timeo, int flags)
+{
+	struct socket *sock = sk->sk_socket;
+	struct dn_scp *scp = DN_SK(sk);
+	int err = -EISCONN;
+	struct flowi fl;
+
+	if (sock->state == SS_CONNECTED)
+		goto out;
+
+	if (sock->state == SS_CONNECTING) {
+		err = 0;
+		if (scp->state == DN_RUN) {
+			sock->state = SS_CONNECTED;
+			goto out;
+		}
+		err = -ECONNREFUSED;
+		if (scp->state != DN_CI && scp->state != DN_CC) {
+			sock->state = SS_UNCONNECTED;
+			goto out;
+		}
+		return dn_wait_run(sk, timeo);
+	}
+
+	err = -EINVAL;
+	if (scp->state != DN_O)
+		goto out;
+
+	if (addr == NULL || addrlen != sizeof(struct sockaddr_dn))
+		goto out;
+	if (addr->sdn_family != AF_DECnet)
+		goto out;
+	if (addr->sdn_flags & SDF_WILD)
+		goto out;
+
+	if (sock_flag(sk, SOCK_ZAPPED)) {
+		err = dn_auto_bind(sk->sk_socket);
+		if (err)
+			goto out;
+	}
+
+	memcpy(&scp->peer, addr, sizeof(struct sockaddr_dn));
+
+	err = -EHOSTUNREACH;
+	memset(&fl, 0, sizeof(fl));
+	fl.oif = sk->sk_bound_dev_if;
+	fl.fld_dst = dn_saddr2dn(&scp->peer);
+	fl.fld_src = dn_saddr2dn(&scp->addr);
+	dn_sk_ports_copy(&fl, scp);
+	fl.proto = DNPROTO_NSP;
+	if (dn_route_output_sock(&sk->sk_dst_cache, &fl, sk, flags) < 0)
+		goto out;
+	sk->sk_route_caps = sk->sk_dst_cache->dev->features;
+	sock->state = SS_CONNECTING;
+	scp->state = DN_CI;
+	scp->segsize_loc = dst_metric(sk->sk_dst_cache, RTAX_ADVMSS);
+
+	dn_nsp_send_conninit(sk, NSP_CI);
+	err = -EINPROGRESS;
+	if (*timeo) {
+		err = dn_wait_run(sk, timeo);
+	}
+out:
+	return err;
+}
+
+static int dn_connect(struct socket *sock, struct sockaddr *uaddr, int addrlen, int flags)
+{
+	struct sockaddr_dn *addr = (struct sockaddr_dn *)uaddr;
+	struct sock *sk = sock->sk;
+	int err;
+	long timeo = sock_sndtimeo(sk, flags & O_NONBLOCK);
+
+	lock_sock(sk);
+	err = __dn_connect(sk, addr, addrlen, &timeo, 0);
+	release_sock(sk);
+
+	return err;
+}
+
+static inline int dn_check_state(struct sock *sk, struct sockaddr_dn *addr, int addrlen, long *timeo, int flags)
+{
+	struct dn_scp *scp = DN_SK(sk);
+
+	switch(scp->state) {
+		case DN_RUN:
+			return 0;
+		case DN_CR:
+			return dn_confirm_accept(sk, timeo, sk->sk_allocation);
+		case DN_CI:
+		case DN_CC:
+			return dn_wait_run(sk, timeo);
+		case DN_O:
+			return __dn_connect(sk, addr, addrlen, timeo, flags);
+	}
+
+	return -EINVAL;
+}
+
+
+static void dn_access_copy(struct sk_buff *skb, struct accessdata_dn *acc)
+{
+        unsigned char *ptr = skb->data;
+
+        acc->acc_userl = *ptr++;
+        memcpy(&acc->acc_user, ptr, acc->acc_userl);
+        ptr += acc->acc_userl;
+
+        acc->acc_passl = *ptr++;
+        memcpy(&acc->acc_pass, ptr, acc->acc_passl);
+        ptr += acc->acc_passl;
+
+        acc->acc_accl = *ptr++;
+        memcpy(&acc->acc_acc, ptr, acc->acc_accl);
+
+        skb_pull(skb, acc->acc_accl + acc->acc_passl + acc->acc_userl + 3);
+
+}
+
+static void dn_user_copy(struct sk_buff *skb, struct optdata_dn *opt)
+{
+        unsigned char *ptr = skb->data;
+        
+        opt->opt_optl   = *ptr++;
+        opt->opt_status = 0;
+        memcpy(opt->opt_data, ptr, opt->opt_optl);
+        skb_pull(skb, opt->opt_optl + 1);
+
+}
+
+static struct sk_buff *dn_wait_for_connect(struct sock *sk, long *timeo)
+{
+	DEFINE_WAIT(wait);
+	struct sk_buff *skb = NULL;
+	int err = 0;
+
+	prepare_to_wait(sk->sk_sleep, &wait, TASK_INTERRUPTIBLE);
+	for(;;) {
+		release_sock(sk);
+		skb = skb_dequeue(&sk->sk_receive_queue);
+		if (skb == NULL) {
+			*timeo = schedule_timeout(*timeo);
+			skb = skb_dequeue(&sk->sk_receive_queue);
+		}
+		lock_sock(sk);
+		if (skb != NULL)
+			break;
+		err = -EINVAL;
+		if (sk->sk_state != TCP_LISTEN)
+			break;
+		err = sock_intr_errno(*timeo);
+		if (signal_pending(current))
+			break;
+		err = -EAGAIN;
+		if (!*timeo)
+			break;
+		prepare_to_wait(sk->sk_sleep, &wait, TASK_INTERRUPTIBLE);
+	}
+	finish_wait(sk->sk_sleep, &wait);
+
+	return skb == NULL ? ERR_PTR(err) : skb;
+}
+
+static int dn_accept(struct socket *sock, struct socket *newsock, int flags)
+{
+	struct sock *sk = sock->sk, *newsk;
+	struct sk_buff *skb = NULL;
+	struct dn_skb_cb *cb;
+	unsigned char menuver;
+	int err = 0;
+	unsigned char type;
+	long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);
+
+	lock_sock(sk);
+
+        if (sk->sk_state != TCP_LISTEN || DN_SK(sk)->state != DN_O) {
+		release_sock(sk);
+		return -EINVAL;
+	}
+
+	skb = skb_dequeue(&sk->sk_receive_queue);
+	if (skb == NULL) {
+		skb = dn_wait_for_connect(sk, &timeo);
+		if (IS_ERR(skb)) {
+			release_sock(sk);
+			return PTR_ERR(skb);
+		}
+	}
+
+	cb = DN_SKB_CB(skb);
+	sk->sk_ack_backlog--;
+	newsk = dn_alloc_sock(newsock, sk->sk_allocation);
+	if (newsk == NULL) {
+		release_sock(sk);
+		kfree_skb(skb);
+		return -ENOBUFS;
+	}
+	release_sock(sk);
+
+	dst_release(xchg(&newsk->sk_dst_cache, skb->dst));
+	skb->dst = NULL;
+
+        DN_SK(newsk)->state        = DN_CR;
+	DN_SK(newsk)->addrrem      = cb->src_port;
+	DN_SK(newsk)->services_rem = cb->services;
+	DN_SK(newsk)->info_rem     = cb->info;
+	DN_SK(newsk)->segsize_rem  = cb->segsize;
+	DN_SK(newsk)->accept_mode  = DN_SK(sk)->accept_mode;
+	
+	if (DN_SK(newsk)->segsize_rem < 230)
+		DN_SK(newsk)->segsize_rem = 230;
+
+	if ((DN_SK(newsk)->services_rem & NSP_FC_MASK) == NSP_FC_NONE)
+		DN_SK(newsk)->max_window = decnet_no_fc_max_cwnd;
+
+	newsk->sk_state  = TCP_LISTEN;
+	memcpy(&(DN_SK(newsk)->addr), &(DN_SK(sk)->addr), sizeof(struct sockaddr_dn));
+
+	/*
+	 * If we are listening on a wild socket, we don't want
+	 * the newly created socket on the wrong hash queue.
+	 */
+	DN_SK(newsk)->addr.sdn_flags &= ~SDF_WILD;
+
+	skb_pull(skb, dn_username2sockaddr(skb->data, skb->len, &(DN_SK(newsk)->addr), &type));
+	skb_pull(skb, dn_username2sockaddr(skb->data, skb->len, &(DN_SK(newsk)->peer), &type));
+	*(dn_address *)(DN_SK(newsk)->peer.sdn_add.a_addr) = cb->src;
+	*(dn_address *)(DN_SK(newsk)->addr.sdn_add.a_addr) = cb->dst;
+
+	menuver = *skb->data;
+	skb_pull(skb, 1);
+
+	if (menuver & DN_MENUVER_ACC)
+		dn_access_copy(skb, &(DN_SK(newsk)->accessdata));
+
+	if (menuver & DN_MENUVER_USR)
+		dn_user_copy(skb, &(DN_SK(newsk)->conndata_in));
+
+	if (menuver & DN_MENUVER_PRX)
+		DN_SK(newsk)->peer.sdn_flags |= SDF_PROXY;
+
+	if (menuver & DN_MENUVER_UIC)
+		DN_SK(newsk)->peer.sdn_flags |= SDF_UICPROXY;
+
+	kfree_skb(skb);
+
+	memcpy(&(DN_SK(newsk)->conndata_out), &(DN_SK(sk)->conndata_out),
+		sizeof(struct optdata_dn));
+	memcpy(&(DN_SK(newsk)->discdata_out), &(DN_SK(sk)->discdata_out),
+		sizeof(struct optdata_dn));
+
+	lock_sock(newsk);
+	err = dn_hash_sock(newsk);
+	if (err == 0) {
+		sock_reset_flag(newsk, SOCK_ZAPPED);
+		dn_send_conn_ack(newsk);
+
+		/*
+	 	 * Here we use sk->sk_allocation since although the conn conf is
+	 	 * for the newsk, the context is the old socket.
+	 	 */
+		if (DN_SK(newsk)->accept_mode == ACC_IMMED)
+			err = dn_confirm_accept(newsk, &timeo,
+						sk->sk_allocation);
+	}
+	release_sock(newsk);
+        return err;
+}
+
+
+static int dn_getname(struct socket *sock, struct sockaddr *uaddr,int *uaddr_len,int peer)
+{
+	struct sockaddr_dn *sa = (struct sockaddr_dn *)uaddr;
+	struct sock *sk = sock->sk;
+	struct dn_scp *scp = DN_SK(sk);
+
+	*uaddr_len = sizeof(struct sockaddr_dn);
+
+	lock_sock(sk);
+
+	if (peer) {
+		if ((sock->state != SS_CONNECTED && 
+		     sock->state != SS_CONNECTING) && 
+		    scp->accept_mode == ACC_IMMED)
+			return -ENOTCONN;
+
+		memcpy(sa, &scp->peer, sizeof(struct sockaddr_dn));
+	} else {
+		memcpy(sa, &scp->addr, sizeof(struct sockaddr_dn));
+	}
+
+	release_sock(sk);
+
+        return 0;
+}
+
+
+static unsigned int dn_poll(struct file *file, struct socket *sock, poll_table  *wait)
+{
+	struct sock *sk = sock->sk;
+	struct dn_scp *scp = DN_SK(sk);
+	int mask = datagram_poll(file, sock, wait);
+
+	if (skb_queue_len(&scp->other_receive_queue))
+		mask |= POLLRDBAND;
+
+	return mask;
+}
+
+static int dn_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
+{
+	struct sock *sk = sock->sk;
+	struct dn_scp *scp = DN_SK(sk);
+	int err = -EOPNOTSUPP;
+	long amount = 0;
+	struct sk_buff *skb;
+	int val;
+
+	switch(cmd)
+	{
+	case SIOCGIFADDR:
+	case SIOCSIFADDR:
+		return dn_dev_ioctl(cmd, (void __user *)arg);
+
+	case SIOCATMARK:
+		lock_sock(sk);
+		val = (skb_queue_len(&scp->other_receive_queue) != 0);
+		if (scp->state != DN_RUN)
+			val = -ENOTCONN;
+		release_sock(sk);
+		return val;
+
+	case TIOCOUTQ:
+		amount = sk->sk_sndbuf - atomic_read(&sk->sk_wmem_alloc);
+		if (amount < 0)
+			amount = 0;
+		err = put_user(amount, (int __user *)arg);
+		break;
+
+	case TIOCINQ:
+		lock_sock(sk);
+		if ((skb = skb_peek(&scp->other_receive_queue)) != NULL) {
+			amount = skb->len;
+		} else {
+			struct sk_buff *skb = sk->sk_receive_queue.next;
+			for(;;) {
+				if (skb ==
+				    (struct sk_buff *)&sk->sk_receive_queue)
+					break;
+				amount += skb->len;
+				skb = skb->next;
+			}
+		}
+		release_sock(sk);
+		err = put_user(amount, (int __user *)arg);
+		break;
+
+	default:
+		err = dev_ioctl(cmd, (void __user *)arg);
+		break;
+	}
+
+	return err;
+}
+
+static int dn_listen(struct socket *sock, int backlog)
+{
+	struct sock *sk = sock->sk;
+	int err = -EINVAL;
+
+	lock_sock(sk);
+
+	if (sock_flag(sk, SOCK_ZAPPED))
+		goto out;
+
+	if ((DN_SK(sk)->state != DN_O) || (sk->sk_state == TCP_LISTEN))
+		goto out;
+
+	sk->sk_max_ack_backlog = backlog;
+	sk->sk_ack_backlog     = 0;
+	sk->sk_state           = TCP_LISTEN;
+	err                 = 0;
+	dn_rehash_sock(sk);
+
+out:
+	release_sock(sk);
+
+        return err;
+}
+
+
+static int dn_shutdown(struct socket *sock, int how)
+{
+	struct sock *sk = sock->sk;
+	struct dn_scp *scp = DN_SK(sk);
+	int err = -ENOTCONN;
+
+	lock_sock(sk);
+
+	if (sock->state == SS_UNCONNECTED)
+		goto out;
+
+	err = 0;
+	if (sock->state == SS_DISCONNECTING)
+		goto out;
+
+	err = -EINVAL;
+	if (scp->state == DN_O)
+		goto out;
+
+	if (how != SHUTDOWN_MASK)
+		goto out;
+
+	sk->sk_shutdown = how;
+	dn_destroy_sock(sk);
+	err = 0;
+
+out:
+	release_sock(sk);
+
+	return err;
+}
+
+static int dn_setsockopt(struct socket *sock, int level, int optname, char __user *optval, int optlen)
+{
+	struct sock *sk = sock->sk;
+	int err;
+
+	lock_sock(sk);
+	err = __dn_setsockopt(sock, level, optname, optval, optlen, 0);
+	release_sock(sk);
+
+	return err;
+}
+
+static int __dn_setsockopt(struct socket *sock, int level,int optname, char __user *optval, int optlen, int flags) 
+{
+	struct	sock *sk = sock->sk;
+	struct dn_scp *scp = DN_SK(sk);
+	long timeo;
+	union {
+		struct optdata_dn opt;
+		struct accessdata_dn acc;
+		int mode;
+		unsigned long win;
+		int val;
+		unsigned char services;
+		unsigned char info;
+	} u;
+	int err;
+
+	if (optlen && !optval)
+		return -EINVAL;
+
+	if (optlen > sizeof(u))
+		return -EINVAL;
+
+	if (copy_from_user(&u, optval, optlen))
+		return -EFAULT;
+
+	switch(optname) {
+		case DSO_CONDATA:
+			if (sock->state == SS_CONNECTED) 
+				return -EISCONN;
+			if ((scp->state != DN_O) && (scp->state != DN_CR))
+				return -EINVAL;
+
+			if (optlen != sizeof(struct optdata_dn))
+				return -EINVAL;
+
+			if (u.opt.opt_optl > 16)
+				return -EINVAL;
+
+			memcpy(&scp->conndata_out, &u.opt, optlen);
+			break;
+
+		case DSO_DISDATA:
+	   	        if (sock->state != SS_CONNECTED && scp->accept_mode == ACC_IMMED)
+				return -ENOTCONN;
+
+			if (optlen != sizeof(struct optdata_dn))
+				return -EINVAL;
+
+			if (u.opt.opt_optl > 16)
+				return -EINVAL;
+
+			memcpy(&scp->discdata_out, &u.opt, optlen);
+			break;
+
+		case DSO_CONACCESS:
+			if (sock->state == SS_CONNECTED) 
+				return -EISCONN;
+			if (scp->state != DN_O)
+				return -EINVAL;
+
+			if (optlen != sizeof(struct accessdata_dn))
+				return -EINVAL;
+
+			if ((u.acc.acc_accl > DN_MAXACCL) ||
+					(u.acc.acc_passl > DN_MAXACCL) ||
+					(u.acc.acc_userl > DN_MAXACCL))
+				return -EINVAL;
+
+			memcpy(&scp->accessdata, &u.acc, optlen);
+			break;
+
+		case DSO_ACCEPTMODE:
+			if (sock->state == SS_CONNECTED)
+				return -EISCONN;
+			if (scp->state != DN_O)
+				return -EINVAL;
+
+			if (optlen != sizeof(int))
+				return -EINVAL;
+
+			if ((u.mode != ACC_IMMED) && (u.mode != ACC_DEFER))
+				return -EINVAL;
+
+			scp->accept_mode = (unsigned char)u.mode;
+			break;
+
+		case DSO_CONACCEPT:
+
+			if (scp->state != DN_CR)
+				return -EINVAL;
+			timeo = sock_rcvtimeo(sk, 0);
+			err = dn_confirm_accept(sk, &timeo, sk->sk_allocation);
+			return err;
+
+		case DSO_CONREJECT:
+
+			if (scp->state != DN_CR)
+				return -EINVAL;
+
+			scp->state = DN_DR;
+			sk->sk_shutdown = SHUTDOWN_MASK;
+			dn_nsp_send_disc(sk, 0x38, 0, sk->sk_allocation);
+			break;
+
+		default:
+#ifdef CONFIG_NETFILTER
+		return nf_setsockopt(sk, PF_DECnet, optname, optval, optlen);
+#endif
+		case DSO_LINKINFO:
+		case DSO_STREAM:
+		case DSO_SEQPACKET:
+			return -ENOPROTOOPT;
+
+		case DSO_MAXWINDOW:
+			if (optlen != sizeof(unsigned long))
+				return -EINVAL;
+			if (u.win > NSP_MAX_WINDOW)
+				u.win = NSP_MAX_WINDOW;
+			if (u.win == 0)
+				return -EINVAL;
+			scp->max_window = u.win;
+			if (scp->snd_window > u.win)
+				scp->snd_window = u.win;
+			break;
+
+		case DSO_NODELAY:
+			if (optlen != sizeof(int))
+				return -EINVAL;
+			if (scp->nonagle == 2)
+				return -EINVAL;
+			scp->nonagle = (u.val == 0) ? 0 : 1;
+			/* if (scp->nonagle == 1) { Push pending frames } */
+			break;
+
+		case DSO_CORK:
+			if (optlen != sizeof(int))
+				return -EINVAL;
+			if (scp->nonagle == 1)
+				return -EINVAL;
+			scp->nonagle = (u.val == 0) ? 0 : 2;
+			/* if (scp->nonagle == 0) { Push pending frames } */
+			break;
+
+		case DSO_SERVICES:
+			if (optlen != sizeof(unsigned char))
+				return -EINVAL;
+			if ((u.services & ~NSP_FC_MASK) != 0x01)
+				return -EINVAL;
+			if ((u.services & NSP_FC_MASK) == NSP_FC_MASK)
+				return -EINVAL;
+			scp->services_loc = u.services;
+			break;
+
+		case DSO_INFO:
+			if (optlen != sizeof(unsigned char))
+				return -EINVAL;
+			if (u.info & 0xfc)
+				return -EINVAL;
+			scp->info_loc = u.info;
+			break;
+	}
+
+	return 0;
+}
+
+static int dn_getsockopt(struct socket *sock, int level, int optname, char __user *optval, int __user *optlen)
+{
+	struct sock *sk = sock->sk;
+	int err;
+
+	lock_sock(sk);
+	err = __dn_getsockopt(sock, level, optname, optval, optlen, 0);
+	release_sock(sk);
+
+	return err;
+}
+
+static int __dn_getsockopt(struct socket *sock, int level,int optname, char __user *optval,int __user *optlen, int flags)
+{
+	struct	sock *sk = sock->sk;
+	struct dn_scp *scp = DN_SK(sk);
+	struct linkinfo_dn link;
+	unsigned int r_len;
+	void *r_data = NULL;
+	unsigned int val;
+
+	if(get_user(r_len , optlen))
+		return -EFAULT;
+		
+	switch(optname) {
+		case DSO_CONDATA:
+			if (r_len > sizeof(struct optdata_dn))
+				r_len = sizeof(struct optdata_dn);
+			r_data = &scp->conndata_in;
+			break;
+
+		case DSO_DISDATA:
+			if (r_len > sizeof(struct optdata_dn))
+				r_len = sizeof(struct optdata_dn);
+			r_data = &scp->discdata_in;
+			break;
+
+		case DSO_CONACCESS:
+			if (r_len > sizeof(struct accessdata_dn))
+				r_len = sizeof(struct accessdata_dn);
+			r_data = &scp->accessdata;
+			break;
+
+		case DSO_ACCEPTMODE:
+			if (r_len > sizeof(unsigned char))
+				r_len = sizeof(unsigned char);
+			r_data = &scp->accept_mode;
+			break;
+
+		case DSO_LINKINFO:
+			if (r_len > sizeof(struct linkinfo_dn))
+				r_len = sizeof(struct linkinfo_dn);
+
+			switch(sock->state) {
+				case SS_CONNECTING:
+					link.idn_linkstate = LL_CONNECTING;
+					break;
+				case SS_DISCONNECTING:
+					link.idn_linkstate = LL_DISCONNECTING;
+					break;
+				case SS_CONNECTED:
+					link.idn_linkstate = LL_RUNNING;
+					break;
+				default:
+					link.idn_linkstate = LL_INACTIVE;
+			}
+
+			link.idn_segsize = scp->segsize_rem;
+			r_data = &link;
+			break;
+
+		default:
+#ifdef CONFIG_NETFILTER
+		{
+			int val, len;
+			
+			if(get_user(len, optlen))
+				return -EFAULT;
+			
+			val = nf_getsockopt(sk, PF_DECnet, optname, 
+							optval, &len);
+			if (val >= 0)
+				val = put_user(len, optlen);
+			return val;
+		}
+#endif
+		case DSO_STREAM:
+		case DSO_SEQPACKET:
+		case DSO_CONACCEPT:
+		case DSO_CONREJECT:
+        		return -ENOPROTOOPT;
+
+		case DSO_MAXWINDOW:
+			if (r_len > sizeof(unsigned long))
+				r_len = sizeof(unsigned long);
+			r_data = &scp->max_window;
+			break;
+
+		case DSO_NODELAY:
+			if (r_len > sizeof(int))
+				r_len = sizeof(int);
+			val = (scp->nonagle == 1);
+			r_data = &val;
+			break;
+
+		case DSO_CORK:
+			if (r_len > sizeof(int))
+				r_len = sizeof(int);
+			val = (scp->nonagle == 2);
+			r_data = &val;
+			break;
+
+		case DSO_SERVICES:
+			if (r_len > sizeof(unsigned char))
+				r_len = sizeof(unsigned char);
+			r_data = &scp->services_rem;
+			break;
+
+		case DSO_INFO:
+			if (r_len > sizeof(unsigned char))
+				r_len = sizeof(unsigned char);
+			r_data = &scp->info_rem;
+			break;
+	}
+
+	if (r_data) {
+		if (copy_to_user(optval, r_data, r_len))
+			return -EFAULT;
+		if (put_user(r_len, optlen))
+			return -EFAULT;
+	}
+
+	return 0;
+}
+
+
+static int dn_data_ready(struct sock *sk, struct sk_buff_head *q, int flags, int target)
+{
+	struct sk_buff *skb = q->next;
+	int len = 0;
+
+	if (flags & MSG_OOB)
+		return skb_queue_len(q) ? 1 : 0;
+
+	while(skb != (struct sk_buff *)q) {
+		struct dn_skb_cb *cb = DN_SKB_CB(skb);
+		len += skb->len;
+
+		if (cb->nsp_flags & 0x40) {
+			/* SOCK_SEQPACKET reads to EOM */
+			if (sk->sk_type == SOCK_SEQPACKET)
+				return 1;
+			/* so does SOCK_STREAM unless WAITALL is specified */
+			if (!(flags & MSG_WAITALL))
+				return 1;
+		}
+
+		/* minimum data length for read exceeded */
+		if (len >= target)
+			return 1;
+
+		skb = skb->next;
+	}
+
+	return 0;
+}
+
+
+static int dn_recvmsg(struct kiocb *iocb, struct socket *sock,
+	struct msghdr *msg, size_t size, int flags)
+{
+	struct sock *sk = sock->sk;
+	struct dn_scp *scp = DN_SK(sk);
+	struct sk_buff_head *queue = &sk->sk_receive_queue;
+	size_t target = size > 1 ? 1 : 0;
+	size_t copied = 0;
+	int rv = 0;
+	struct sk_buff *skb, *nskb;
+	struct dn_skb_cb *cb = NULL;
+	unsigned char eor = 0;
+	long timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT);
+
+	lock_sock(sk);
+
+	if (sock_flag(sk, SOCK_ZAPPED)) {
+		rv = -EADDRNOTAVAIL;
+		goto out;
+	}
+
+	rv = dn_check_state(sk, NULL, 0, &timeo, flags);
+	if (rv)
+		goto out;
+
+	if (sk->sk_shutdown & RCV_SHUTDOWN) {
+		if (!(flags & MSG_NOSIGNAL))
+			send_sig(SIGPIPE, current, 0);
+		rv = -EPIPE;
+		goto out;
+	}
+
+	if (flags & ~(MSG_PEEK|MSG_OOB|MSG_WAITALL|MSG_DONTWAIT|MSG_NOSIGNAL)) {
+		rv = -EOPNOTSUPP;
+		goto out;
+	}
+
+	if (flags & MSG_OOB)
+		queue = &scp->other_receive_queue;
+
+	if (flags & MSG_WAITALL)
+		target = size;
+
+
+	/*
+	 * See if there is data ready to read, sleep if there isn't
+	 */
+	for(;;) {
+		if (sk->sk_err)
+			goto out;
+
+		if (skb_queue_len(&scp->other_receive_queue)) {
+			if (!(flags & MSG_OOB)) {
+				msg->msg_flags |= MSG_OOB;
+				if (!scp->other_report) {
+					scp->other_report = 1;
+					goto out;
+				}
+			}
+		}
+		
+		if (scp->state != DN_RUN)
+			goto out;
+
+		if (signal_pending(current)) {
+			rv = sock_intr_errno(timeo);
+			goto out;
+		}
+
+		if (dn_data_ready(sk, queue, flags, target))
+			break;
+
+		if (flags & MSG_DONTWAIT) {
+			rv = -EWOULDBLOCK;
+			goto out;
+		}
+
+		set_bit(SOCK_ASYNC_WAITDATA, &sock->flags);
+		SOCK_SLEEP_PRE(sk)
+
+		if (!dn_data_ready(sk, queue, flags, target))
+			schedule();
+
+		SOCK_SLEEP_POST(sk)
+		clear_bit(SOCK_ASYNC_WAITDATA, &sock->flags);
+	}
+
+	for(skb = queue->next; skb != (struct sk_buff *)queue; skb = nskb) {
+		unsigned int chunk = skb->len;
+		cb = DN_SKB_CB(skb);
+
+		if ((chunk + copied) > size)
+			chunk = size - copied;
+
+		if (memcpy_toiovec(msg->msg_iov, skb->data, chunk)) {
+			rv = -EFAULT;
+			break;
+		}
+		copied += chunk;
+
+		if (!(flags & MSG_PEEK))
+			skb_pull(skb, chunk);
+
+		eor = cb->nsp_flags & 0x40;
+		nskb = skb->next;
+
+		if (skb->len == 0) {
+			skb_unlink(skb);
+			kfree_skb(skb);
+			/* 
+			 * N.B. Don't refer to skb or cb after this point
+			 * in loop.
+			 */
+			if ((scp->flowloc_sw == DN_DONTSEND) && !dn_congested(sk)) {
+				scp->flowloc_sw = DN_SEND;
+				dn_nsp_send_link(sk, DN_SEND, 0);
+			}
+		}
+
+		if (eor) { 
+			if (sk->sk_type == SOCK_SEQPACKET)
+				break;
+			if (!(flags & MSG_WAITALL))
+				break;
+		}
+
+		if (flags & MSG_OOB)
+			break;
+
+		if (copied >= target)
+			break;
+	}
+
+	rv = copied;
+
+
+	if (eor && (sk->sk_type == SOCK_SEQPACKET))
+		msg->msg_flags |= MSG_EOR;
+
+out:
+	if (rv == 0)
+		rv = (flags & MSG_PEEK) ? -sk->sk_err : sock_error(sk);
+
+	if ((rv >= 0) && msg->msg_name) {
+		memcpy(msg->msg_name, &scp->peer, sizeof(struct sockaddr_dn));
+		msg->msg_namelen = sizeof(struct sockaddr_dn);
+	}
+
+	release_sock(sk);
+
+	return rv;
+}
+
+
+static inline int dn_queue_too_long(struct dn_scp *scp, struct sk_buff_head *queue, int flags)
+{
+	unsigned char fctype = scp->services_rem & NSP_FC_MASK;
+	if (skb_queue_len(queue) >= scp->snd_window)
+		return 1;
+	if (fctype != NSP_FC_NONE) {
+		if (flags & MSG_OOB) {
+			if (scp->flowrem_oth == 0)
+				return 1;
+		} else {
+			if (scp->flowrem_dat == 0)
+				return 1;
+		}
+	}
+	return 0;
+}
+
+/*
+ * The DECnet spec requires the the "routing layer" accepts packets which
+ * are at least 230 bytes in size. This excludes any headers which the NSP
+ * layer might add, so we always assume that we'll be using the maximal
+ * length header on data packets. The variation in length is due to the
+ * inclusion (or not) of the two 16 bit acknowledgement fields so it doesn't
+ * make much practical difference.
+ */
+unsigned dn_mss_from_pmtu(struct net_device *dev, int mtu)
+{
+	unsigned mss = 230 - DN_MAX_NSP_DATA_HEADER;
+	if (dev) {
+		struct dn_dev *dn_db = dev->dn_ptr;
+		mtu -= LL_RESERVED_SPACE(dev);
+		if (dn_db->use_long)
+			mtu -= 21;
+		else
+			mtu -= 6;
+		mtu -= DN_MAX_NSP_DATA_HEADER;
+	} else {
+		/*
+		 * 21 = long header, 16 = guess at MAC header length
+		 */
+		mtu -= (21 + DN_MAX_NSP_DATA_HEADER + 16);
+	}
+	if (mtu > mss)
+		mss = mtu;
+	return mss;
+}
+
+static inline unsigned int dn_current_mss(struct sock *sk, int flags)
+{
+	struct dst_entry *dst = __sk_dst_get(sk);
+	struct dn_scp *scp = DN_SK(sk);
+	int mss_now = min_t(int, scp->segsize_loc, scp->segsize_rem);
+
+	/* Other data messages are limited to 16 bytes per packet */
+	if (flags & MSG_OOB)
+		return 16;
+
+	/* This works out the maximum size of segment we can send out */
+	if (dst) {
+		u32 mtu = dst_mtu(dst);
+		mss_now = min_t(int, dn_mss_from_pmtu(dst->dev, mtu), mss_now);
+	}
+
+	return mss_now;
+}
+
+static int dn_error(struct sock *sk, int flags, int err)
+{
+	if (err == -EPIPE)
+		err = sock_error(sk) ? : -EPIPE;
+	if (err == -EPIPE && !(flags & MSG_NOSIGNAL))
+		send_sig(SIGPIPE, current, 0);
+	return err;
+}
+
+static int dn_sendmsg(struct kiocb *iocb, struct socket *sock,
+	   struct msghdr *msg, size_t size)
+{
+	struct sock *sk = sock->sk;
+	struct dn_scp *scp = DN_SK(sk);
+	size_t mss;
+	struct sk_buff_head *queue = &scp->data_xmit_queue;
+	int flags = msg->msg_flags;
+	int err = 0;
+	size_t sent = 0;
+	int addr_len = msg->msg_namelen;
+	struct sockaddr_dn *addr = (struct sockaddr_dn *)msg->msg_name;
+	struct sk_buff *skb = NULL;
+	struct dn_skb_cb *cb;
+	size_t len;
+	unsigned char fctype;
+	long timeo = sock_sndtimeo(sk, flags & MSG_DONTWAIT);
+
+	if (flags & ~(MSG_TRYHARD|MSG_OOB|MSG_DONTWAIT|MSG_EOR|MSG_NOSIGNAL|MSG_MORE|MSG_CMSG_COMPAT))
+		return -EOPNOTSUPP;
+
+	if (addr_len && (addr_len != sizeof(struct sockaddr_dn)))
+		return -EINVAL;
+
+	/*
+	 * The only difference between stream sockets and sequenced packet
+	 * sockets is that the stream sockets always behave as if MSG_EOR
+	 * has been set.
+	 */
+	if (sock->type == SOCK_STREAM) {
+		if (flags & MSG_EOR)
+			return -EINVAL;
+		flags |= MSG_EOR;
+	}
+
+	lock_sock(sk);
+
+	err = dn_check_state(sk, addr, addr_len, &timeo, flags);
+	if (err)
+		goto out_err;
+
+	if (sk->sk_shutdown & SEND_SHUTDOWN) {
+		err = -EPIPE;
+		goto out_err;
+	}
+
+	if ((flags & MSG_TRYHARD) && sk->sk_dst_cache)
+		dst_negative_advice(&sk->sk_dst_cache);
+
+	mss = scp->segsize_rem;
+	fctype = scp->services_rem & NSP_FC_MASK;
+
+	mss = dn_current_mss(sk, flags);
+
+	if (flags & MSG_OOB) {
+		queue = &scp->other_xmit_queue;
+		if (size > mss) {
+			err = -EMSGSIZE;
+			goto out;
+		}
+	}
+
+	scp->persist_fxn = dn_nsp_xmit_timeout;
+
+	while(sent < size) {
+		err = sock_error(sk);
+		if (err)
+			goto out;
+
+		if (signal_pending(current)) {
+			err = sock_intr_errno(timeo);
+			goto out;
+		}
+
+		/*
+		 * Calculate size that we wish to send.
+		 */
+		len = size - sent;
+
+		if (len > mss)
+			len = mss;
+
+		/*
+		 * Wait for queue size to go down below the window
+		 * size.
+		 */
+		if (dn_queue_too_long(scp, queue, flags)) {
+			if (flags & MSG_DONTWAIT) {
+				err = -EWOULDBLOCK;
+				goto out;
+			}
+
+			SOCK_SLEEP_PRE(sk)
+
+			if (dn_queue_too_long(scp, queue, flags))
+				schedule();
+
+			SOCK_SLEEP_POST(sk)
+
+			continue;
+		}
+
+		/*
+		 * Get a suitably sized skb.
+		 */
+		skb = dn_alloc_send_skb(sk, &len, flags & MSG_DONTWAIT, timeo, &err);
+
+		if (err)
+			break;
+
+		if (!skb)
+			continue;
+
+		cb = DN_SKB_CB(skb);
+
+		skb_reserve(skb, DN_MAX_NSP_DATA_HEADER);
+
+		if (memcpy_fromiovec(skb_put(skb, len), msg->msg_iov, len)) {
+			err = -EFAULT;
+			goto out;
+		}
+
+		if (flags & MSG_OOB) {
+			cb->nsp_flags = 0x30;
+			if (fctype != NSP_FC_NONE)
+				scp->flowrem_oth--;
+		} else {
+			cb->nsp_flags = 0x00;
+			if (scp->seg_total == 0)
+				cb->nsp_flags |= 0x20;
+
+			scp->seg_total += len;
+		
+			if (((sent + len) == size) && (flags & MSG_EOR)) {
+				cb->nsp_flags |= 0x40;
+				scp->seg_total = 0;
+				if (fctype == NSP_FC_SCMC)
+					scp->flowrem_dat--;
+			}
+			if (fctype == NSP_FC_SRC)
+				scp->flowrem_dat--;
+		}
+
+		sent += len;
+		dn_nsp_queue_xmit(sk, skb, sk->sk_allocation, flags & MSG_OOB);
+		skb = NULL;
+
+		scp->persist = dn_nsp_persist(sk);
+
+	}
+out:
+
+	if (skb)
+		kfree_skb(skb);
+
+	release_sock(sk);
+
+	return sent ? sent : err;
+
+out_err:
+	err = dn_error(sk, flags, err);
+	release_sock(sk);
+	return err;
+}
+
+static int dn_device_event(struct notifier_block *this, unsigned long event,
+			void *ptr)
+{
+	struct net_device *dev = (struct net_device *)ptr;
+
+	switch(event) {
+		case NETDEV_UP:
+			dn_dev_up(dev);
+			break;
+		case NETDEV_DOWN:
+			dn_dev_down(dev);
+			break;
+		default:
+			break;
+	}
+
+	return NOTIFY_DONE;
+}
+
+static struct notifier_block dn_dev_notifier = {
+	.notifier_call = dn_device_event,
+};
+
+extern int dn_route_rcv(struct sk_buff *, struct net_device *, struct packet_type *);
+
+static struct packet_type dn_dix_packet_type = {
+	.type =		__constant_htons(ETH_P_DNA_RT),
+	.dev =		NULL,		/* All devices */
+	.func =		dn_route_rcv,
+};
+
+#ifdef CONFIG_PROC_FS
+struct dn_iter_state {
+	int bucket;
+};
+
+static struct sock *dn_socket_get_first(struct seq_file *seq)
+{
+	struct dn_iter_state *state = seq->private;
+	struct sock *n = NULL;
+
+	for(state->bucket = 0;
+	    state->bucket < DN_SK_HASH_SIZE;
+	    ++state->bucket) {
+		n = sk_head(&dn_sk_hash[state->bucket]);
+		if (n)
+			break;
+	}
+
+	return n;
+}
+
+static struct sock *dn_socket_get_next(struct seq_file *seq,
+				       struct sock *n)
+{
+	struct dn_iter_state *state = seq->private;
+
+	n = sk_next(n);
+try_again:
+	if (n)
+		goto out;
+	if (++state->bucket >= DN_SK_HASH_SIZE)
+		goto out;
+	n = sk_head(&dn_sk_hash[state->bucket]);
+	goto try_again;
+out:
+	return n;
+}
+
+static struct sock *socket_get_idx(struct seq_file *seq, loff_t *pos)
+{
+	struct sock *sk = dn_socket_get_first(seq);
+
+	if (sk) {
+		while(*pos && (sk = dn_socket_get_next(seq, sk)))
+			--*pos;
+	}
+	return *pos ? NULL : sk;
+}
+
+static void *dn_socket_get_idx(struct seq_file *seq, loff_t pos)
+{
+	void *rc;
+	read_lock_bh(&dn_hash_lock);
+	rc = socket_get_idx(seq, &pos);
+	if (!rc) {
+		read_unlock_bh(&dn_hash_lock);
+	}
+	return rc;
+}
+
+static void *dn_socket_seq_start(struct seq_file *seq, loff_t *pos)
+{
+	return *pos ? dn_socket_get_idx(seq, *pos - 1) : SEQ_START_TOKEN;
+}
+
+static void *dn_socket_seq_next(struct seq_file *seq, void *v, loff_t *pos)
+{
+	void *rc;
+
+	if (v == SEQ_START_TOKEN) {
+		rc = dn_socket_get_idx(seq, 0);
+		goto out;
+	}
+
+	rc = dn_socket_get_next(seq, v);
+	if (rc)
+		goto out;
+	read_unlock_bh(&dn_hash_lock);
+out:
+	++*pos;
+	return rc;
+}
+
+static void dn_socket_seq_stop(struct seq_file *seq, void *v)
+{
+	if (v && v != SEQ_START_TOKEN)
+		read_unlock_bh(&dn_hash_lock);
+}
+
+#define IS_NOT_PRINTABLE(x) ((x) < 32 || (x) > 126)
+
+static void dn_printable_object(struct sockaddr_dn *dn, unsigned char *buf)
+{
+	int i;
+    
+	switch (dn_ntohs(dn->sdn_objnamel)) {
+		case 0:
+			sprintf(buf, "%d", dn->sdn_objnum);
+			break;
+		default:
+			for (i = 0; i < dn_ntohs(dn->sdn_objnamel); i++) {
+				buf[i] = dn->sdn_objname[i];
+				if (IS_NOT_PRINTABLE(buf[i]))
+					buf[i] = '.';
+			}
+			buf[i] = 0;
+    	}
+}
+
+static char *dn_state2asc(unsigned char state)
+{
+	switch(state) {
+		case DN_O:
+			return "OPEN";
+		case DN_CR:
+			return "  CR";
+		case DN_DR:
+			return "  DR";
+		case DN_DRC:
+			return " DRC";
+		case DN_CC:
+			return "  CC";
+		case DN_CI:
+			return "  CI";
+		case DN_NR:
+			return "  NR";
+		case DN_NC:
+			return "  NC";
+		case DN_CD:
+			return "  CD";
+		case DN_RJ:
+			return "  RJ";
+		case DN_RUN:
+			return " RUN";
+		case DN_DI:
+			return "  DI";
+		case DN_DIC:
+			return " DIC";
+		case DN_DN:
+			return "  DN";
+		case DN_CL:
+			return "  CL";
+		case DN_CN:
+			return "  CN";
+	}
+
+	return "????";
+}
+
+static inline void dn_socket_format_entry(struct seq_file *seq, struct sock *sk)
+{
+	struct dn_scp *scp = DN_SK(sk);
+	char buf1[DN_ASCBUF_LEN];
+	char buf2[DN_ASCBUF_LEN];
+	char local_object[DN_MAXOBJL+3];
+	char remote_object[DN_MAXOBJL+3];
+
+	dn_printable_object(&scp->addr, local_object);
+	dn_printable_object(&scp->peer, remote_object);
+
+	seq_printf(seq,
+		   "%6s/%04X %04d:%04d %04d:%04d %01d %-16s "
+		   "%6s/%04X %04d:%04d %04d:%04d %01d %-16s %4s %s\n",
+		   dn_addr2asc(dn_ntohs(dn_saddr2dn(&scp->addr)), buf1),
+		   scp->addrloc,
+		   scp->numdat,
+		   scp->numoth,
+		   scp->ackxmt_dat,
+		   scp->ackxmt_oth,
+		   scp->flowloc_sw,
+		   local_object,
+		   dn_addr2asc(dn_ntohs(dn_saddr2dn(&scp->peer)), buf2),
+		   scp->addrrem,
+		   scp->numdat_rcv,
+		   scp->numoth_rcv,
+		   scp->ackrcv_dat,
+		   scp->ackrcv_oth,
+		   scp->flowrem_sw,
+		   remote_object,
+		   dn_state2asc(scp->state),
+		   ((scp->accept_mode == ACC_IMMED) ? "IMMED" : "DEFER"));
+}
+
+static int dn_socket_seq_show(struct seq_file *seq, void *v)
+{
+	if (v == SEQ_START_TOKEN) {
+		seq_puts(seq, "Local                                              Remote\n");
+	} else {
+		dn_socket_format_entry(seq, v);
+	}
+	return 0;
+}
+
+static struct seq_operations dn_socket_seq_ops = {
+	.start	= dn_socket_seq_start,
+	.next	= dn_socket_seq_next,
+	.stop	= dn_socket_seq_stop,
+	.show	= dn_socket_seq_show,
+};
+
+static int dn_socket_seq_open(struct inode *inode, struct file *file)
+{
+	struct seq_file *seq;
+	int rc = -ENOMEM;
+	struct dn_iter_state *s = kmalloc(sizeof(*s), GFP_KERNEL);
+
+	if (!s)
+		goto out;
+
+	rc = seq_open(file, &dn_socket_seq_ops);
+	if (rc)
+		goto out_kfree;
+
+	seq		= file->private_data;
+	seq->private	= s;
+	memset(s, 0, sizeof(*s));
+out:
+	return rc;
+out_kfree:
+	kfree(s);
+	goto out;
+}
+
+static struct file_operations dn_socket_seq_fops = {
+	.owner		= THIS_MODULE,
+	.open		= dn_socket_seq_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= seq_release_private,
+};
+#endif
+
+static struct net_proto_family	dn_family_ops = {
+	.family =	AF_DECnet,
+	.create =	dn_create,
+	.owner	=	THIS_MODULE,
+};
+
+static struct proto_ops dn_proto_ops = {
+	.family =	AF_DECnet,
+	.owner =	THIS_MODULE,
+	.release =	dn_release,
+	.bind =		dn_bind,
+	.connect =	dn_connect,
+	.socketpair =	sock_no_socketpair,
+	.accept =	dn_accept,
+	.getname =	dn_getname,
+	.poll =		dn_poll,
+	.ioctl =	dn_ioctl,
+	.listen =	dn_listen,
+	.shutdown =	dn_shutdown,
+	.setsockopt =	dn_setsockopt,
+	.getsockopt =	dn_getsockopt,
+	.sendmsg =	dn_sendmsg,
+	.recvmsg =	dn_recvmsg,
+	.mmap =		sock_no_mmap,
+	.sendpage =	sock_no_sendpage,
+};
+
+void dn_register_sysctl(void);
+void dn_unregister_sysctl(void);
+
+MODULE_DESCRIPTION("The Linux DECnet Network Protocol");
+MODULE_AUTHOR("Linux DECnet Project Team");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_NETPROTO(PF_DECnet);
+
+static char banner[] __initdata = KERN_INFO "NET4: DECnet for Linux: V.2.5.68s (C) 1995-2003 Linux DECnet Project Team\n";
+
+static int __init decnet_init(void)
+{
+	int rc;
+
+        printk(banner);
+
+	rc = proto_register(&dn_proto, 1);
+	if (rc != 0)
+		goto out;
+
+	dn_neigh_init();
+	dn_dev_init();
+	dn_route_init();
+	dn_fib_init();
+
+	sock_register(&dn_family_ops);
+	dev_add_pack(&dn_dix_packet_type);
+	register_netdevice_notifier(&dn_dev_notifier);
+
+	proc_net_fops_create("decnet", S_IRUGO, &dn_socket_seq_fops);
+	dn_register_sysctl();
+out:
+	return rc;
+
+}
+module_init(decnet_init);
+
+/*
+ * Prevent DECnet module unloading until its fixed properly.
+ * Requires an audit of the code to check for memory leaks and
+ * initialisation problems etc.
+ */
+#if 0
+static void __exit decnet_exit(void)
+{
+	sock_unregister(AF_DECnet);
+	dev_remove_pack(&dn_dix_packet_type);
+
+	dn_unregister_sysctl();
+
+	unregister_netdevice_notifier(&dn_dev_notifier);
+
+	dn_route_cleanup();
+	dn_dev_cleanup();
+	dn_neigh_cleanup();
+	dn_fib_cleanup();
+
+	proc_net_remove("decnet");
+
+	proto_unregister(&dn_proto);
+}
+module_exit(decnet_exit);
+#endif
diff --git a/net/decnet/dn_dev.c b/net/decnet/dn_dev.c
new file mode 100644
index 0000000..c2a0346
--- /dev/null
+++ b/net/decnet/dn_dev.c
@@ -0,0 +1,1481 @@
+/*
+ * DECnet       An implementation of the DECnet protocol suite for the LINUX
+ *              operating system.  DECnet is implemented using the  BSD Socket
+ *              interface as the means of communication with the user level.
+ *
+ *              DECnet Device Layer
+ *
+ * Authors:     Steve Whitehouse <SteveW@ACM.org>
+ *              Eduardo Marcelo Serrat <emserrat@geocities.com>
+ *
+ * Changes:
+ *          Steve Whitehouse : Devices now see incoming frames so they
+ *                             can mark on who it came from.
+ *          Steve Whitehouse : Fixed bug in creating neighbours. Each neighbour
+ *                             can now have a device specific setup func.
+ *          Steve Whitehouse : Added /proc/sys/net/decnet/conf/<dev>/
+ *          Steve Whitehouse : Fixed bug which sometimes killed timer
+ *          Steve Whitehouse : Multiple ifaddr support
+ *          Steve Whitehouse : SIOCGIFCONF is now a compile time option
+ *          Steve Whitehouse : /proc/sys/net/decnet/conf/<sys>/forwarding
+ *          Steve Whitehouse : Removed timer1 - it's a user space issue now
+ *         Patrick Caulfield : Fixed router hello message format
+ *          Steve Whitehouse : Got rid of constant sizes for blksize for
+ *                             devices. All mtu based now.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/net.h>
+#include <linux/netdevice.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/if_arp.h>
+#include <linux/if_ether.h>
+#include <linux/skbuff.h>
+#include <linux/rtnetlink.h>
+#include <linux/sysctl.h>
+#include <linux/notifier.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <net/neighbour.h>
+#include <net/dst.h>
+#include <net/flow.h>
+#include <net/dn.h>
+#include <net/dn_dev.h>
+#include <net/dn_route.h>
+#include <net/dn_neigh.h>
+#include <net/dn_fib.h>
+
+#define DN_IFREQ_SIZE (sizeof(struct ifreq) - sizeof(struct sockaddr) + sizeof(struct sockaddr_dn))
+
+static char dn_rt_all_end_mcast[ETH_ALEN] = {0xAB,0x00,0x00,0x04,0x00,0x00};
+static char dn_rt_all_rt_mcast[ETH_ALEN]  = {0xAB,0x00,0x00,0x03,0x00,0x00};
+static char dn_hiord[ETH_ALEN]            = {0xAA,0x00,0x04,0x00,0x00,0x00};
+static unsigned char dn_eco_version[3]    = {0x02,0x00,0x00};
+
+extern struct neigh_table dn_neigh_table;
+
+/*
+ * decnet_address is kept in network order.
+ */
+dn_address decnet_address = 0;
+
+static DEFINE_RWLOCK(dndev_lock);
+static struct net_device *decnet_default_device;
+static struct notifier_block *dnaddr_chain;
+
+static struct dn_dev *dn_dev_create(struct net_device *dev, int *err);
+static void dn_dev_delete(struct net_device *dev);
+static void rtmsg_ifa(int event, struct dn_ifaddr *ifa);
+
+static int dn_eth_up(struct net_device *);
+static void dn_eth_down(struct net_device *);
+static void dn_send_brd_hello(struct net_device *dev, struct dn_ifaddr *ifa);
+static void dn_send_ptp_hello(struct net_device *dev, struct dn_ifaddr *ifa);
+
+static struct dn_dev_parms dn_dev_list[] =  {
+{
+	.type =		ARPHRD_ETHER, /* Ethernet */
+	.mode =		DN_DEV_BCAST,
+	.state =	DN_DEV_S_RU,
+	.t2 =		1,
+	.t3 =		10,
+	.name =		"ethernet",
+	.ctl_name =	NET_DECNET_CONF_ETHER,
+	.up =		dn_eth_up,
+	.down = 	dn_eth_down,
+	.timer3 =	dn_send_brd_hello,
+},
+{
+	.type =		ARPHRD_IPGRE, /* DECnet tunneled over GRE in IP */
+	.mode =		DN_DEV_BCAST,
+	.state =	DN_DEV_S_RU,
+	.t2 =		1,
+	.t3 =		10,
+	.name =		"ipgre",
+	.ctl_name =	NET_DECNET_CONF_GRE,
+	.timer3 =	dn_send_brd_hello,
+},
+#if 0
+{
+	.type =		ARPHRD_X25, /* Bog standard X.25 */
+	.mode =		DN_DEV_UCAST,
+	.state =	DN_DEV_S_DS,
+	.t2 =		1,
+	.t3 =		120,
+	.name =		"x25",
+	.ctl_name =	NET_DECNET_CONF_X25,
+	.timer3 =	dn_send_ptp_hello,
+},
+#endif
+#if 0
+{
+	.type =		ARPHRD_PPP, /* DECnet over PPP */
+	.mode =		DN_DEV_BCAST,
+	.state =	DN_DEV_S_RU,
+	.t2 =		1,
+	.t3 =		10,
+	.name =		"ppp",
+	.ctl_name =	NET_DECNET_CONF_PPP,
+	.timer3 =	dn_send_brd_hello,
+},
+#endif
+{
+	.type =		ARPHRD_DDCMP, /* DECnet over DDCMP */
+	.mode =		DN_DEV_UCAST,
+	.state =	DN_DEV_S_DS,
+	.t2 =		1,
+	.t3 =		120,
+	.name =		"ddcmp",
+	.ctl_name =	NET_DECNET_CONF_DDCMP,
+	.timer3 =	dn_send_ptp_hello,
+},
+{
+	.type =		ARPHRD_LOOPBACK, /* Loopback interface - always last */
+	.mode =		DN_DEV_BCAST,
+	.state =	DN_DEV_S_RU,
+	.t2 =		1,
+	.t3 =		10,
+	.name =		"loopback",
+	.ctl_name =	NET_DECNET_CONF_LOOPBACK,
+	.timer3 =	dn_send_brd_hello,
+}
+};
+
+#define DN_DEV_LIST_SIZE (sizeof(dn_dev_list)/sizeof(struct dn_dev_parms))
+
+#define DN_DEV_PARMS_OFFSET(x) ((int) ((char *) &((struct dn_dev_parms *)0)->x))
+
+#ifdef CONFIG_SYSCTL
+
+static int min_t2[] = { 1 };
+static int max_t2[] = { 60 }; /* No max specified, but this seems sensible */
+static int min_t3[] = { 1 };
+static int max_t3[] = { 8191 }; /* Must fit in 16 bits when multiplied by BCT3MULT or T3MULT */
+
+static int min_priority[1];
+static int max_priority[] = { 127 }; /* From DECnet spec */
+
+static int dn_forwarding_proc(ctl_table *, int, struct file *,
+			void __user *, size_t *, loff_t *);
+static int dn_forwarding_sysctl(ctl_table *table, int __user *name, int nlen,
+			void __user *oldval, size_t __user *oldlenp,
+			void __user *newval, size_t newlen,
+			void **context);
+
+static struct dn_dev_sysctl_table {
+	struct ctl_table_header *sysctl_header;
+	ctl_table dn_dev_vars[5];
+	ctl_table dn_dev_dev[2];
+	ctl_table dn_dev_conf_dir[2];
+	ctl_table dn_dev_proto_dir[2];
+	ctl_table dn_dev_root_dir[2];
+} dn_dev_sysctl = {
+	NULL,
+	{
+	{
+		.ctl_name = NET_DECNET_CONF_DEV_FORWARDING,
+		.procname = "forwarding",
+		.data = (void *)DN_DEV_PARMS_OFFSET(forwarding),
+		.maxlen = sizeof(int),
+		.mode = 0644,
+		.proc_handler = dn_forwarding_proc,
+		.strategy = dn_forwarding_sysctl,
+	},
+	{
+		.ctl_name = NET_DECNET_CONF_DEV_PRIORITY,
+		.procname = "priority",
+		.data = (void *)DN_DEV_PARMS_OFFSET(priority),
+		.maxlen = sizeof(int),
+		.mode = 0644,
+		.proc_handler = proc_dointvec_minmax,
+		.strategy = sysctl_intvec,
+		.extra1 = &min_priority,
+		.extra2 = &max_priority
+	},
+	{
+		.ctl_name = NET_DECNET_CONF_DEV_T2,
+		.procname = "t2",
+		.data = (void *)DN_DEV_PARMS_OFFSET(t2),
+		.maxlen = sizeof(int),
+		.mode = 0644,
+		.proc_handler = proc_dointvec_minmax,
+		.strategy = sysctl_intvec,
+		.extra1 = &min_t2,
+		.extra2 = &max_t2
+	},
+	{
+		.ctl_name = NET_DECNET_CONF_DEV_T3,
+		.procname = "t3",
+		.data = (void *)DN_DEV_PARMS_OFFSET(t3),
+		.maxlen = sizeof(int),
+		.mode = 0644,
+		.proc_handler = proc_dointvec_minmax,
+		.strategy = sysctl_intvec,
+		.extra1 = &min_t3,
+		.extra2 = &max_t3
+	},
+	{0}
+	},
+	{{
+		.ctl_name = 0, 
+		.procname = "", 
+		.mode = 0555, 
+		.child = dn_dev_sysctl.dn_dev_vars
+	}, {0}},
+	{{
+		.ctl_name = NET_DECNET_CONF,
+		.procname = "conf", 
+		.mode = 0555, 
+		.child = dn_dev_sysctl.dn_dev_dev
+	}, {0}},
+	{{
+		.ctl_name = NET_DECNET, 
+		.procname = "decnet", 
+		.mode = 0555, 
+		.child = dn_dev_sysctl.dn_dev_conf_dir
+	}, {0}},
+	{{
+		.ctl_name = CTL_NET, 
+		.procname = "net", 
+		.mode = 0555, 
+		.child = dn_dev_sysctl.dn_dev_proto_dir
+	}, {0}}
+};
+
+static void dn_dev_sysctl_register(struct net_device *dev, struct dn_dev_parms *parms)
+{
+	struct dn_dev_sysctl_table *t;
+	int i;
+
+	t = kmalloc(sizeof(*t), GFP_KERNEL);
+	if (t == NULL)
+		return;
+
+	memcpy(t, &dn_dev_sysctl, sizeof(*t));
+
+	for(i = 0; i < ARRAY_SIZE(t->dn_dev_vars) - 1; i++) {
+		long offset = (long)t->dn_dev_vars[i].data;
+		t->dn_dev_vars[i].data = ((char *)parms) + offset;
+		t->dn_dev_vars[i].de = NULL;
+	}
+
+	if (dev) {
+		t->dn_dev_dev[0].procname = dev->name;
+		t->dn_dev_dev[0].ctl_name = dev->ifindex;
+	} else {
+		t->dn_dev_dev[0].procname = parms->name;
+		t->dn_dev_dev[0].ctl_name = parms->ctl_name;
+	}
+
+	t->dn_dev_dev[0].child = t->dn_dev_vars;
+	t->dn_dev_dev[0].de = NULL;
+	t->dn_dev_conf_dir[0].child = t->dn_dev_dev;
+	t->dn_dev_conf_dir[0].de = NULL;
+	t->dn_dev_proto_dir[0].child = t->dn_dev_conf_dir;
+	t->dn_dev_proto_dir[0].de = NULL;
+	t->dn_dev_root_dir[0].child = t->dn_dev_proto_dir;
+	t->dn_dev_root_dir[0].de = NULL;
+	t->dn_dev_vars[0].extra1 = (void *)dev;
+
+	t->sysctl_header = register_sysctl_table(t->dn_dev_root_dir, 0);
+	if (t->sysctl_header == NULL)
+		kfree(t);
+	else
+		parms->sysctl = t;
+}
+
+static void dn_dev_sysctl_unregister(struct dn_dev_parms *parms)
+{
+	if (parms->sysctl) {
+		struct dn_dev_sysctl_table *t = parms->sysctl;
+		parms->sysctl = NULL;
+		unregister_sysctl_table(t->sysctl_header);
+		kfree(t);
+	}
+}
+
+static int dn_forwarding_proc(ctl_table *table, int write, 
+				struct file *filep,
+				void __user *buffer,
+				size_t *lenp, loff_t *ppos)
+{
+#ifdef CONFIG_DECNET_ROUTER
+	struct net_device *dev = table->extra1;
+	struct dn_dev *dn_db;
+	int err;
+	int tmp, old;
+
+	if (table->extra1 == NULL)
+		return -EINVAL;
+
+	dn_db = dev->dn_ptr;
+	old = dn_db->parms.forwarding;
+
+	err = proc_dointvec(table, write, filep, buffer, lenp, ppos);
+
+	if ((err >= 0) && write) {
+		if (dn_db->parms.forwarding < 0)
+			dn_db->parms.forwarding = 0;
+		if (dn_db->parms.forwarding > 2)
+			dn_db->parms.forwarding = 2;
+		/*
+		 * What an ugly hack this is... its works, just. It
+		 * would be nice if sysctl/proc were just that little
+		 * bit more flexible so I don't have to write a special
+		 * routine, or suffer hacks like this - SJW
+		 */
+		tmp = dn_db->parms.forwarding;
+		dn_db->parms.forwarding = old;
+		if (dn_db->parms.down)
+			dn_db->parms.down(dev);
+		dn_db->parms.forwarding = tmp;
+		if (dn_db->parms.up)
+			dn_db->parms.up(dev);
+	}
+
+	return err;
+#else
+	return -EINVAL;
+#endif
+}
+
+static int dn_forwarding_sysctl(ctl_table *table, int __user *name, int nlen,
+			void __user *oldval, size_t __user *oldlenp,
+			void __user *newval, size_t newlen,
+			void **context)
+{
+#ifdef CONFIG_DECNET_ROUTER
+	struct net_device *dev = table->extra1;
+	struct dn_dev *dn_db;
+	int value;
+
+	if (table->extra1 == NULL)
+		return -EINVAL;
+
+	dn_db = dev->dn_ptr;
+
+	if (newval && newlen) {
+		if (newlen != sizeof(int))
+			return -EINVAL;
+
+		if (get_user(value, (int __user *)newval))
+			return -EFAULT;
+		if (value < 0)
+			return -EINVAL;
+		if (value > 2)
+			return -EINVAL;
+
+		if (dn_db->parms.down)
+			dn_db->parms.down(dev);
+		dn_db->parms.forwarding = value;
+		if (dn_db->parms.up)
+			dn_db->parms.up(dev);
+	}
+
+	return 0;
+#else
+	return -EINVAL;
+#endif
+}
+
+#else /* CONFIG_SYSCTL */
+static void dn_dev_sysctl_unregister(struct dn_dev_parms *parms)
+{
+}
+static void dn_dev_sysctl_register(struct net_device *dev, struct dn_dev_parms *parms)
+{
+}
+
+#endif /* CONFIG_SYSCTL */
+
+static inline __u16 mtu2blksize(struct net_device *dev)
+{
+	u32 blksize = dev->mtu;
+	if (blksize > 0xffff)
+		blksize = 0xffff;
+
+	if (dev->type == ARPHRD_ETHER ||
+	    dev->type == ARPHRD_PPP ||
+	    dev->type == ARPHRD_IPGRE ||
+	    dev->type == ARPHRD_LOOPBACK)
+		blksize -= 2;
+
+	return (__u16)blksize;
+}
+
+static struct dn_ifaddr *dn_dev_alloc_ifa(void)
+{
+	struct dn_ifaddr *ifa;
+
+	ifa = kmalloc(sizeof(*ifa), GFP_KERNEL);
+
+	if (ifa) {
+		memset(ifa, 0, sizeof(*ifa));
+	}
+
+	return ifa;
+}
+
+static __inline__ void dn_dev_free_ifa(struct dn_ifaddr *ifa)
+{
+	kfree(ifa);
+}
+
+static void dn_dev_del_ifa(struct dn_dev *dn_db, struct dn_ifaddr **ifap, int destroy)
+{
+	struct dn_ifaddr *ifa1 = *ifap;
+	unsigned char mac_addr[6];
+	struct net_device *dev = dn_db->dev;
+
+	ASSERT_RTNL();
+
+	*ifap = ifa1->ifa_next;
+
+	if (dn_db->dev->type == ARPHRD_ETHER) {
+		if (ifa1->ifa_local != dn_htons(dn_eth2dn(dev->dev_addr))) {
+			dn_dn2eth(mac_addr, ifa1->ifa_local);
+			dev_mc_delete(dev, mac_addr, ETH_ALEN, 0);
+		}
+	}
+
+	rtmsg_ifa(RTM_DELADDR, ifa1);
+	notifier_call_chain(&dnaddr_chain, NETDEV_DOWN, ifa1);
+	if (destroy) {
+		dn_dev_free_ifa(ifa1);
+
+		if (dn_db->ifa_list == NULL)
+			dn_dev_delete(dn_db->dev);
+	}
+}
+
+static int dn_dev_insert_ifa(struct dn_dev *dn_db, struct dn_ifaddr *ifa)
+{
+	struct net_device *dev = dn_db->dev;
+	struct dn_ifaddr *ifa1;
+	unsigned char mac_addr[6];
+
+	ASSERT_RTNL();
+
+	/* Check for duplicates */	
+	for(ifa1 = dn_db->ifa_list; ifa1; ifa1 = ifa1->ifa_next) {
+		if (ifa1->ifa_local == ifa->ifa_local)
+			return -EEXIST;
+	}
+
+	if (dev->type == ARPHRD_ETHER) {
+		if (ifa->ifa_local != dn_htons(dn_eth2dn(dev->dev_addr))) {
+			dn_dn2eth(mac_addr, ifa->ifa_local);
+			dev_mc_add(dev, mac_addr, ETH_ALEN, 0);
+			dev_mc_upload(dev);
+		}
+	}
+
+	ifa->ifa_next = dn_db->ifa_list;
+	dn_db->ifa_list = ifa;
+
+	rtmsg_ifa(RTM_NEWADDR, ifa);
+	notifier_call_chain(&dnaddr_chain, NETDEV_UP, ifa);
+
+	return 0;
+}
+
+static int dn_dev_set_ifa(struct net_device *dev, struct dn_ifaddr *ifa)
+{
+	struct dn_dev *dn_db = dev->dn_ptr;
+	int rv;
+
+	if (dn_db == NULL) {
+		int err;
+		dn_db = dn_dev_create(dev, &err);
+		if (dn_db == NULL)
+			return err;
+	}
+
+	ifa->ifa_dev = dn_db;
+
+	if (dev->flags & IFF_LOOPBACK)
+		ifa->ifa_scope = RT_SCOPE_HOST;
+
+	rv = dn_dev_insert_ifa(dn_db, ifa);
+	if (rv)
+		dn_dev_free_ifa(ifa);
+	return rv;
+}
+
+
+int dn_dev_ioctl(unsigned int cmd, void __user *arg)
+{
+	char buffer[DN_IFREQ_SIZE];
+	struct ifreq *ifr = (struct ifreq *)buffer;
+	struct sockaddr_dn *sdn = (struct sockaddr_dn *)&ifr->ifr_addr;
+	struct dn_dev *dn_db;
+	struct net_device *dev;
+	struct dn_ifaddr *ifa = NULL, **ifap = NULL;
+	int ret = 0;
+
+	if (copy_from_user(ifr, arg, DN_IFREQ_SIZE))
+		return -EFAULT;
+	ifr->ifr_name[IFNAMSIZ-1] = 0;
+
+#ifdef CONFIG_KMOD
+	dev_load(ifr->ifr_name);
+#endif
+
+	switch(cmd) {
+		case SIOCGIFADDR:
+			break;
+		case SIOCSIFADDR:
+			if (!capable(CAP_NET_ADMIN))
+				return -EACCES;
+			if (sdn->sdn_family != AF_DECnet)
+				return -EINVAL;
+			break;
+		default:
+			return -EINVAL;
+	}
+
+	rtnl_lock();
+
+	if ((dev = __dev_get_by_name(ifr->ifr_name)) == NULL) {
+		ret = -ENODEV;
+		goto done;
+	}
+
+	if ((dn_db = dev->dn_ptr) != NULL) {
+		for (ifap = &dn_db->ifa_list; (ifa=*ifap) != NULL; ifap = &ifa->ifa_next)
+			if (strcmp(ifr->ifr_name, ifa->ifa_label) == 0)
+				break;
+	}
+
+	if (ifa == NULL && cmd != SIOCSIFADDR) {
+		ret = -EADDRNOTAVAIL;
+		goto done;
+	}
+
+	switch(cmd) {
+		case SIOCGIFADDR:
+			*((dn_address *)sdn->sdn_nodeaddr) = ifa->ifa_local;
+			goto rarok;
+
+		case SIOCSIFADDR:
+			if (!ifa) {
+				if ((ifa = dn_dev_alloc_ifa()) == NULL) {
+					ret = -ENOBUFS;
+					break;
+				}
+				memcpy(ifa->ifa_label, dev->name, IFNAMSIZ);
+			} else {
+				if (ifa->ifa_local == dn_saddr2dn(sdn))
+					break;
+				dn_dev_del_ifa(dn_db, ifap, 0);
+			}
+
+			ifa->ifa_local = ifa->ifa_address = dn_saddr2dn(sdn);
+
+			ret = dn_dev_set_ifa(dev, ifa);
+	}
+done:
+	rtnl_unlock();
+
+	return ret;
+rarok:
+	if (copy_to_user(arg, ifr, DN_IFREQ_SIZE))
+		ret = -EFAULT;
+	goto done;
+}
+
+struct net_device *dn_dev_get_default(void)
+{
+	struct net_device *dev;
+	read_lock(&dndev_lock);
+	dev = decnet_default_device;
+	if (dev) {
+		if (dev->dn_ptr)
+			dev_hold(dev);
+		else
+			dev = NULL;
+	}
+	read_unlock(&dndev_lock);
+	return dev;
+}
+
+int dn_dev_set_default(struct net_device *dev, int force)
+{
+	struct net_device *old = NULL;
+	int rv = -EBUSY;
+	if (!dev->dn_ptr)
+		return -ENODEV;
+	write_lock(&dndev_lock);
+	if (force || decnet_default_device == NULL) {
+		old = decnet_default_device;
+		decnet_default_device = dev;
+		rv = 0;
+	}
+	write_unlock(&dndev_lock);
+	if (old)
+		dev_put(dev);
+	return rv;
+}
+
+static void dn_dev_check_default(struct net_device *dev)
+{
+	write_lock(&dndev_lock);
+	if (dev == decnet_default_device) {
+		decnet_default_device = NULL;
+	} else {
+		dev = NULL;
+	}
+	write_unlock(&dndev_lock);
+	if (dev)
+		dev_put(dev);
+}
+
+static struct dn_dev *dn_dev_by_index(int ifindex)
+{
+	struct net_device *dev;
+	struct dn_dev *dn_dev = NULL;
+	dev = dev_get_by_index(ifindex);
+	if (dev) {
+		dn_dev = dev->dn_ptr;
+		dev_put(dev);
+	}
+
+	return dn_dev;
+}
+
+static int dn_dev_rtm_deladdr(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
+{
+	struct rtattr **rta = arg;
+	struct dn_dev *dn_db;
+	struct ifaddrmsg *ifm = NLMSG_DATA(nlh);
+	struct dn_ifaddr *ifa, **ifap;
+
+	if ((dn_db = dn_dev_by_index(ifm->ifa_index)) == NULL)
+		return -EADDRNOTAVAIL;
+
+	for(ifap = &dn_db->ifa_list; (ifa=*ifap) != NULL; ifap = &ifa->ifa_next) {
+		void *tmp = rta[IFA_LOCAL-1];
+		if ((tmp && memcmp(RTA_DATA(tmp), &ifa->ifa_local, 2)) ||
+		    (rta[IFA_LABEL-1] && rtattr_strcmp(rta[IFA_LABEL-1], ifa->ifa_label)))
+			continue;
+
+		dn_dev_del_ifa(dn_db, ifap, 1);
+		return 0;
+	}
+
+	return -EADDRNOTAVAIL;
+}
+
+static int dn_dev_rtm_newaddr(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
+{
+	struct rtattr **rta = arg;
+	struct net_device *dev;
+	struct dn_dev *dn_db;
+	struct ifaddrmsg *ifm = NLMSG_DATA(nlh);
+	struct dn_ifaddr *ifa;
+	int rv;
+
+	if (rta[IFA_LOCAL-1] == NULL)
+		return -EINVAL;
+
+	if ((dev = __dev_get_by_index(ifm->ifa_index)) == NULL)
+		return -ENODEV;
+
+	if ((dn_db = dev->dn_ptr) == NULL) {
+		int err;
+		dn_db = dn_dev_create(dev, &err);
+		if (!dn_db)
+			return err;
+	}
+	
+	if ((ifa = dn_dev_alloc_ifa()) == NULL)
+		return -ENOBUFS;
+
+	if (!rta[IFA_ADDRESS - 1])
+		rta[IFA_ADDRESS - 1] = rta[IFA_LOCAL - 1];
+	memcpy(&ifa->ifa_local, RTA_DATA(rta[IFA_LOCAL-1]), 2);
+	memcpy(&ifa->ifa_address, RTA_DATA(rta[IFA_ADDRESS-1]), 2);
+	ifa->ifa_flags = ifm->ifa_flags;
+	ifa->ifa_scope = ifm->ifa_scope;
+	ifa->ifa_dev = dn_db;
+	if (rta[IFA_LABEL-1])
+		rtattr_strlcpy(ifa->ifa_label, rta[IFA_LABEL-1], IFNAMSIZ);
+	else
+		memcpy(ifa->ifa_label, dev->name, IFNAMSIZ);
+
+	rv = dn_dev_insert_ifa(dn_db, ifa);
+	if (rv)
+		dn_dev_free_ifa(ifa);
+	return rv;
+}
+
+static int dn_dev_fill_ifaddr(struct sk_buff *skb, struct dn_ifaddr *ifa,
+				u32 pid, u32 seq, int event)
+{
+	struct ifaddrmsg *ifm;
+	struct nlmsghdr *nlh;
+	unsigned char *b = skb->tail;
+
+	nlh = NLMSG_PUT(skb, pid, seq, event, sizeof(*ifm));
+	ifm = NLMSG_DATA(nlh);
+
+	ifm->ifa_family = AF_DECnet;
+	ifm->ifa_prefixlen = 16;
+	ifm->ifa_flags = ifa->ifa_flags | IFA_F_PERMANENT;
+	ifm->ifa_scope = ifa->ifa_scope;
+	ifm->ifa_index = ifa->ifa_dev->dev->ifindex;
+	if (ifa->ifa_address)
+		RTA_PUT(skb, IFA_ADDRESS, 2, &ifa->ifa_address);
+	if (ifa->ifa_local)
+		RTA_PUT(skb, IFA_LOCAL, 2, &ifa->ifa_local);
+	if (ifa->ifa_label[0])
+		RTA_PUT(skb, IFA_LABEL, IFNAMSIZ, &ifa->ifa_label);
+	nlh->nlmsg_len = skb->tail - b;
+	return skb->len;
+
+nlmsg_failure:
+rtattr_failure:
+        skb_trim(skb, b - skb->data);
+        return -1;
+}
+
+static void rtmsg_ifa(int event, struct dn_ifaddr *ifa)
+{
+	struct sk_buff *skb;
+	int size = NLMSG_SPACE(sizeof(struct ifaddrmsg)+128);
+
+	skb = alloc_skb(size, GFP_KERNEL);
+	if (!skb) {
+		netlink_set_err(rtnl, 0, RTMGRP_DECnet_IFADDR, ENOBUFS);
+		return;
+	}
+	if (dn_dev_fill_ifaddr(skb, ifa, 0, 0, event) < 0) {
+		kfree_skb(skb);
+		netlink_set_err(rtnl, 0, RTMGRP_DECnet_IFADDR, EINVAL);
+		return;
+	}
+	NETLINK_CB(skb).dst_groups = RTMGRP_DECnet_IFADDR;
+	netlink_broadcast(rtnl, skb, 0, RTMGRP_DECnet_IFADDR, GFP_KERNEL);
+}
+
+static int dn_dev_dump_ifaddr(struct sk_buff *skb, struct netlink_callback *cb)
+{
+	int idx, dn_idx;
+	int s_idx, s_dn_idx;
+	struct net_device *dev;
+	struct dn_dev *dn_db;
+	struct dn_ifaddr *ifa;
+
+	s_idx = cb->args[0];
+	s_dn_idx = dn_idx = cb->args[1];
+	read_lock(&dev_base_lock);
+	for(dev = dev_base, idx = 0; dev; dev = dev->next, idx++) {
+		if (idx < s_idx)
+			continue;
+		if (idx > s_idx)
+			s_dn_idx = 0;
+		if ((dn_db = dev->dn_ptr) == NULL)
+			continue;
+
+		for(ifa = dn_db->ifa_list, dn_idx = 0; ifa; ifa = ifa->ifa_next, dn_idx++) {
+			if (dn_idx < s_dn_idx)
+				continue;
+
+			if (dn_dev_fill_ifaddr(skb, ifa,
+					       NETLINK_CB(cb->skb).pid,
+					       cb->nlh->nlmsg_seq,
+					       RTM_NEWADDR) <= 0)
+				goto done;
+		}
+	}
+done:
+	read_unlock(&dev_base_lock);
+	cb->args[0] = idx;
+	cb->args[1] = dn_idx;
+
+	return skb->len;
+}
+
+static int dn_dev_get_first(struct net_device *dev, dn_address *addr)
+{
+	struct dn_dev *dn_db = (struct dn_dev *)dev->dn_ptr;
+	struct dn_ifaddr *ifa;
+	int rv = -ENODEV;
+	if (dn_db == NULL)
+		goto out;
+	ifa = dn_db->ifa_list;
+	if (ifa != NULL) {
+		*addr = ifa->ifa_local;
+		rv = 0;
+	}
+out:
+	return rv;
+}
+
+/* 
+ * Find a default address to bind to.
+ *
+ * This is one of those areas where the initial VMS concepts don't really
+ * map onto the Linux concepts, and since we introduced multiple addresses
+ * per interface we have to cope with slightly odd ways of finding out what
+ * "our address" really is. Mostly it's not a problem; for this we just guess
+ * a sensible default. Eventually the routing code will take care of all the
+ * nasties for us I hope.
+ */
+int dn_dev_bind_default(dn_address *addr)
+{
+	struct net_device *dev;
+	int rv;
+	dev = dn_dev_get_default();
+last_chance:
+	if (dev) {
+		read_lock(&dev_base_lock);
+		rv = dn_dev_get_first(dev, addr);
+		read_unlock(&dev_base_lock);
+		dev_put(dev);
+		if (rv == 0 || dev == &loopback_dev)
+			return rv;
+	}
+	dev = &loopback_dev;
+	dev_hold(dev);
+	goto last_chance;
+}
+
+static void dn_send_endnode_hello(struct net_device *dev, struct dn_ifaddr *ifa)
+{
+        struct endnode_hello_message *msg;
+        struct sk_buff *skb = NULL;
+        unsigned short int *pktlen;
+	struct dn_dev *dn_db = (struct dn_dev *)dev->dn_ptr;
+
+        if ((skb = dn_alloc_skb(NULL, sizeof(*msg), GFP_ATOMIC)) == NULL)
+		return;
+
+        skb->dev = dev;
+
+        msg = (struct endnode_hello_message *)skb_put(skb,sizeof(*msg));
+
+        msg->msgflg  = 0x0D;
+        memcpy(msg->tiver, dn_eco_version, 3);
+	dn_dn2eth(msg->id, ifa->ifa_local);
+        msg->iinfo   = DN_RT_INFO_ENDN;
+        msg->blksize = dn_htons(mtu2blksize(dev));
+        msg->area    = 0x00;
+        memset(msg->seed, 0, 8);
+        memcpy(msg->neighbor, dn_hiord, ETH_ALEN);
+
+	if (dn_db->router) {
+		struct dn_neigh *dn = (struct dn_neigh *)dn_db->router;
+		dn_dn2eth(msg->neighbor, dn->addr);
+	}
+
+        msg->timer   = dn_htons((unsigned short)dn_db->parms.t3);
+        msg->mpd     = 0x00;
+        msg->datalen = 0x02;
+        memset(msg->data, 0xAA, 2);
+        
+        pktlen = (unsigned short *)skb_push(skb,2);
+        *pktlen = dn_htons(skb->len - 2);
+
+	skb->nh.raw = skb->data;
+
+	dn_rt_finish_output(skb, dn_rt_all_rt_mcast, msg->id);
+}
+
+
+#define DRDELAY (5 * HZ)
+
+static int dn_am_i_a_router(struct dn_neigh *dn, struct dn_dev *dn_db, struct dn_ifaddr *ifa)
+{
+	/* First check time since device went up */
+	if ((jiffies - dn_db->uptime) < DRDELAY)
+		return 0;
+
+	/* If there is no router, then yes... */
+	if (!dn_db->router)
+		return 1;
+
+	/* otherwise only if we have a higher priority or.. */
+	if (dn->priority < dn_db->parms.priority)
+		return 1;
+
+	/* if we have equal priority and a higher node number */
+	if (dn->priority != dn_db->parms.priority)
+		return 0;
+
+	if (dn_ntohs(dn->addr) < dn_ntohs(ifa->ifa_local))
+		return 1;
+
+	return 0;
+}
+
+static void dn_send_router_hello(struct net_device *dev, struct dn_ifaddr *ifa)
+{
+	int n;
+	struct dn_dev *dn_db = dev->dn_ptr;
+	struct dn_neigh *dn = (struct dn_neigh *)dn_db->router;
+	struct sk_buff *skb;
+	size_t size;
+	unsigned char *ptr;
+	unsigned char *i1, *i2;
+	unsigned short *pktlen;
+	char *src;
+
+	if (mtu2blksize(dev) < (26 + 7))
+		return;
+
+	n = mtu2blksize(dev) - 26;
+	n /= 7;
+
+	if (n > 32)
+		n = 32;
+
+	size = 2 + 26 + 7 * n;
+
+	if ((skb = dn_alloc_skb(NULL, size, GFP_ATOMIC)) == NULL)
+		return;
+
+	skb->dev = dev;
+	ptr = skb_put(skb, size);
+
+	*ptr++ = DN_RT_PKT_CNTL | DN_RT_PKT_ERTH;
+	*ptr++ = 2; /* ECO */
+	*ptr++ = 0;
+	*ptr++ = 0;
+	dn_dn2eth(ptr, ifa->ifa_local);
+	src = ptr;
+	ptr += ETH_ALEN;
+	*ptr++ = dn_db->parms.forwarding == 1 ? 
+			DN_RT_INFO_L1RT : DN_RT_INFO_L2RT;
+	*((unsigned short *)ptr) = dn_htons(mtu2blksize(dev));
+	ptr += 2;
+	*ptr++ = dn_db->parms.priority; /* Priority */ 
+	*ptr++ = 0; /* Area: Reserved */
+	*((unsigned short *)ptr) = dn_htons((unsigned short)dn_db->parms.t3);
+	ptr += 2;
+	*ptr++ = 0; /* MPD: Reserved */
+	i1 = ptr++;
+	memset(ptr, 0, 7); /* Name: Reserved */
+	ptr += 7;
+	i2 = ptr++;
+
+	n = dn_neigh_elist(dev, ptr, n);
+
+	*i2 = 7 * n;
+	*i1 = 8 + *i2;
+
+	skb_trim(skb, (27 + *i2));
+
+	pktlen = (unsigned short *)skb_push(skb, 2);
+	*pktlen = dn_htons(skb->len - 2);
+
+	skb->nh.raw = skb->data;
+
+	if (dn_am_i_a_router(dn, dn_db, ifa)) {
+		struct sk_buff *skb2 = skb_copy(skb, GFP_ATOMIC);
+		if (skb2) {
+			dn_rt_finish_output(skb2, dn_rt_all_end_mcast, src);
+		}
+	}
+
+	dn_rt_finish_output(skb, dn_rt_all_rt_mcast, src);
+}
+
+static void dn_send_brd_hello(struct net_device *dev, struct dn_ifaddr *ifa)
+{
+	struct dn_dev *dn_db = (struct dn_dev *)dev->dn_ptr;
+
+	if (dn_db->parms.forwarding == 0)
+		dn_send_endnode_hello(dev, ifa);
+	else
+		dn_send_router_hello(dev, ifa);
+}
+
+static void dn_send_ptp_hello(struct net_device *dev, struct dn_ifaddr *ifa)
+{
+	int tdlen = 16;
+	int size = dev->hard_header_len + 2 + 4 + tdlen;
+	struct sk_buff *skb = dn_alloc_skb(NULL, size, GFP_ATOMIC);
+	int i;
+	unsigned char *ptr;
+	char src[ETH_ALEN];
+
+	if (skb == NULL)
+		return ;
+
+	skb->dev = dev;
+	skb_push(skb, dev->hard_header_len);
+	ptr = skb_put(skb, 2 + 4 + tdlen);
+
+	*ptr++ = DN_RT_PKT_HELO;
+	*((dn_address *)ptr) = ifa->ifa_local;
+	ptr += 2;
+	*ptr++ = tdlen;
+
+	for(i = 0; i < tdlen; i++)
+		*ptr++ = 0252;
+
+	dn_dn2eth(src, ifa->ifa_local);
+	dn_rt_finish_output(skb, dn_rt_all_rt_mcast, src);
+}
+
+static int dn_eth_up(struct net_device *dev)
+{
+	struct dn_dev *dn_db = dev->dn_ptr;
+
+	if (dn_db->parms.forwarding == 0)
+		dev_mc_add(dev, dn_rt_all_end_mcast, ETH_ALEN, 0);
+	else
+		dev_mc_add(dev, dn_rt_all_rt_mcast, ETH_ALEN, 0);
+
+	dev_mc_upload(dev);
+
+	dn_db->use_long = 1;
+
+	return 0;
+}
+
+static void dn_eth_down(struct net_device *dev)
+{
+	struct dn_dev *dn_db = dev->dn_ptr;
+
+	if (dn_db->parms.forwarding == 0)
+		dev_mc_delete(dev, dn_rt_all_end_mcast, ETH_ALEN, 0);
+	else
+		dev_mc_delete(dev, dn_rt_all_rt_mcast, ETH_ALEN, 0);
+}
+
+static void dn_dev_set_timer(struct net_device *dev);
+
+static void dn_dev_timer_func(unsigned long arg)
+{
+	struct net_device *dev = (struct net_device *)arg;
+	struct dn_dev *dn_db = dev->dn_ptr;
+	struct dn_ifaddr *ifa;
+
+	if (dn_db->t3 <= dn_db->parms.t2) {
+		if (dn_db->parms.timer3) {
+			for(ifa = dn_db->ifa_list; ifa; ifa = ifa->ifa_next) {
+				if (!(ifa->ifa_flags & IFA_F_SECONDARY))
+					dn_db->parms.timer3(dev, ifa);
+			}
+		}
+		dn_db->t3 = dn_db->parms.t3;
+	} else {
+		dn_db->t3 -= dn_db->parms.t2;
+	}
+
+	dn_dev_set_timer(dev);
+}
+
+static void dn_dev_set_timer(struct net_device *dev)
+{
+	struct dn_dev *dn_db = dev->dn_ptr;
+
+	if (dn_db->parms.t2 > dn_db->parms.t3)
+		dn_db->parms.t2 = dn_db->parms.t3;
+
+	dn_db->timer.data = (unsigned long)dev;
+	dn_db->timer.function = dn_dev_timer_func;
+	dn_db->timer.expires = jiffies + (dn_db->parms.t2 * HZ);
+
+	add_timer(&dn_db->timer);
+}
+
+struct dn_dev *dn_dev_create(struct net_device *dev, int *err)
+{
+	int i;
+	struct dn_dev_parms *p = dn_dev_list;
+	struct dn_dev *dn_db;
+
+	for(i = 0; i < DN_DEV_LIST_SIZE; i++, p++) {
+		if (p->type == dev->type)
+			break;
+	}
+
+	*err = -ENODEV;
+	if (i == DN_DEV_LIST_SIZE)
+		return NULL;
+
+	*err = -ENOBUFS;
+	if ((dn_db = kmalloc(sizeof(struct dn_dev), GFP_ATOMIC)) == NULL)
+		return NULL;
+
+	memset(dn_db, 0, sizeof(struct dn_dev));
+	memcpy(&dn_db->parms, p, sizeof(struct dn_dev_parms));
+	smp_wmb();
+	dev->dn_ptr = dn_db;
+	dn_db->dev = dev;
+	init_timer(&dn_db->timer);
+
+	dn_db->uptime = jiffies;
+	if (dn_db->parms.up) {
+		if (dn_db->parms.up(dev) < 0) {
+			dev->dn_ptr = NULL;
+			kfree(dn_db);
+			return NULL;
+		}
+	}
+
+	dn_db->neigh_parms = neigh_parms_alloc(dev, &dn_neigh_table);
+
+	dn_dev_sysctl_register(dev, &dn_db->parms);
+
+	dn_dev_set_timer(dev);
+
+	*err = 0;
+	return dn_db;
+}
+
+
+/*
+ * This processes a device up event. We only start up
+ * the loopback device & ethernet devices with correct
+ * MAC addreses automatically. Others must be started
+ * specifically.
+ *
+ * FIXME: How should we configure the loopback address ? If we could dispense
+ * with using decnet_address here and for autobind, it will be one less thing
+ * for users to worry about setting up.
+ */
+
+void dn_dev_up(struct net_device *dev)
+{
+	struct dn_ifaddr *ifa;
+	dn_address addr = decnet_address;
+	int maybe_default = 0;
+	struct dn_dev *dn_db = (struct dn_dev *)dev->dn_ptr;
+
+	if ((dev->type != ARPHRD_ETHER) && (dev->type != ARPHRD_LOOPBACK))
+		return;
+
+	/*
+	 * Need to ensure that loopback device has a dn_db attached to it
+	 * to allow creation of neighbours against it, even though it might
+	 * not have a local address of its own. Might as well do the same for
+	 * all autoconfigured interfaces.
+	 */
+	if (dn_db == NULL) {
+		int err;
+		dn_db = dn_dev_create(dev, &err);
+		if (dn_db == NULL)
+			return;
+	}
+
+	if (dev->type == ARPHRD_ETHER) {
+		if (memcmp(dev->dev_addr, dn_hiord, 4) != 0)
+			return;
+		addr = dn_htons(dn_eth2dn(dev->dev_addr));
+		maybe_default = 1;
+	}
+
+	if (addr == 0)
+		return;
+
+	if ((ifa = dn_dev_alloc_ifa()) == NULL)
+		return;
+
+	ifa->ifa_local = ifa->ifa_address = addr;
+	ifa->ifa_flags = 0;
+	ifa->ifa_scope = RT_SCOPE_UNIVERSE;
+	strcpy(ifa->ifa_label, dev->name);
+
+	dn_dev_set_ifa(dev, ifa);
+
+	/*
+	 * Automagically set the default device to the first automatically
+	 * configured ethernet card in the system.
+	 */
+	if (maybe_default) {
+		dev_hold(dev);
+		if (dn_dev_set_default(dev, 0))
+			dev_put(dev);
+	}
+}
+
+static void dn_dev_delete(struct net_device *dev)
+{
+	struct dn_dev *dn_db = dev->dn_ptr;
+
+	if (dn_db == NULL)
+		return;
+
+	del_timer_sync(&dn_db->timer);
+	dn_dev_sysctl_unregister(&dn_db->parms);
+	dn_dev_check_default(dev);
+	neigh_ifdown(&dn_neigh_table, dev);
+
+	if (dn_db->parms.down)
+		dn_db->parms.down(dev);
+
+	dev->dn_ptr = NULL;
+
+	neigh_parms_release(&dn_neigh_table, dn_db->neigh_parms);
+	neigh_ifdown(&dn_neigh_table, dev);
+
+	if (dn_db->router)
+		neigh_release(dn_db->router);
+	if (dn_db->peer)
+		neigh_release(dn_db->peer);
+
+	kfree(dn_db);
+}
+
+void dn_dev_down(struct net_device *dev)
+{
+	struct dn_dev *dn_db = dev->dn_ptr;
+	struct dn_ifaddr *ifa;
+
+	if (dn_db == NULL)
+		return;
+
+	while((ifa = dn_db->ifa_list) != NULL) {
+		dn_dev_del_ifa(dn_db, &dn_db->ifa_list, 0);
+		dn_dev_free_ifa(ifa);
+	}
+
+	dn_dev_delete(dev);
+}
+
+void dn_dev_init_pkt(struct sk_buff *skb)
+{
+	return;
+}
+
+void dn_dev_veri_pkt(struct sk_buff *skb)
+{
+	return;
+}
+
+void dn_dev_hello(struct sk_buff *skb)
+{
+	return;
+}
+
+void dn_dev_devices_off(void)
+{
+	struct net_device *dev;
+
+	rtnl_lock();
+	for(dev = dev_base; dev; dev = dev->next)
+		dn_dev_down(dev);
+	rtnl_unlock();
+
+}
+
+void dn_dev_devices_on(void)
+{
+	struct net_device *dev;
+
+	rtnl_lock();
+	for(dev = dev_base; dev; dev = dev->next) {
+		if (dev->flags & IFF_UP)
+			dn_dev_up(dev);
+	}
+	rtnl_unlock();
+}
+
+int register_dnaddr_notifier(struct notifier_block *nb)
+{
+	return notifier_chain_register(&dnaddr_chain, nb);
+}
+
+int unregister_dnaddr_notifier(struct notifier_block *nb)
+{
+	return notifier_chain_unregister(&dnaddr_chain, nb);
+}
+
+#ifdef CONFIG_PROC_FS
+static inline struct net_device *dn_dev_get_next(struct seq_file *seq, struct net_device *dev)
+{
+	do {
+		dev = dev->next;
+	} while(dev && !dev->dn_ptr);
+
+	return dev;
+}
+
+static struct net_device *dn_dev_get_idx(struct seq_file *seq, loff_t pos)
+{
+	struct net_device *dev;
+
+	dev = dev_base;
+	if (dev && !dev->dn_ptr)
+		dev = dn_dev_get_next(seq, dev);
+	if (pos) {
+		while(dev && (dev = dn_dev_get_next(seq, dev)))
+			--pos;
+	}
+	return dev;
+}
+
+static void *dn_dev_seq_start(struct seq_file *seq, loff_t *pos)
+{
+	if (*pos) {
+		struct net_device *dev;
+		read_lock(&dev_base_lock);
+		dev = dn_dev_get_idx(seq, *pos - 1);
+		if (dev == NULL)
+			read_unlock(&dev_base_lock);
+		return dev;
+	}
+	return SEQ_START_TOKEN;
+}
+
+static void *dn_dev_seq_next(struct seq_file *seq, void *v, loff_t *pos)
+{
+	struct net_device *dev = v;
+	loff_t one = 1;
+
+	if (v == SEQ_START_TOKEN) {
+		dev = dn_dev_seq_start(seq, &one);
+	} else {
+		dev = dn_dev_get_next(seq, dev);
+		if (dev == NULL)
+			read_unlock(&dev_base_lock);
+	}
+	++*pos;
+	return dev;
+}
+
+static void dn_dev_seq_stop(struct seq_file *seq, void *v)
+{
+	if (v && v != SEQ_START_TOKEN)
+		read_unlock(&dev_base_lock);
+}
+
+static char *dn_type2asc(char type)
+{
+	switch(type) {
+		case DN_DEV_BCAST:
+			return "B";
+		case DN_DEV_UCAST:
+			return "U";
+		case DN_DEV_MPOINT:
+			return "M";
+	}
+
+	return "?";
+}
+
+static int dn_dev_seq_show(struct seq_file *seq, void *v)
+{
+	if (v == SEQ_START_TOKEN)
+        	seq_puts(seq, "Name     Flags T1   Timer1 T3   Timer3 BlkSize Pri State DevType    Router Peer\n");
+	else {
+		struct net_device *dev = v;
+		char peer_buf[DN_ASCBUF_LEN];
+		char router_buf[DN_ASCBUF_LEN];
+		struct dn_dev *dn_db = dev->dn_ptr;
+
+                seq_printf(seq, "%-8s %1s     %04u %04u   %04lu %04lu"
+				"   %04hu    %03d %02x    %-10s %-7s %-7s\n",
+                             	dev->name ? dev->name : "???",
+                             	dn_type2asc(dn_db->parms.mode),
+                             	0, 0,
+				dn_db->t3, dn_db->parms.t3,
+				mtu2blksize(dev),
+				dn_db->parms.priority,
+				dn_db->parms.state, dn_db->parms.name,
+				dn_db->router ? dn_addr2asc(dn_ntohs(*(dn_address *)dn_db->router->primary_key), router_buf) : "",
+				dn_db->peer ? dn_addr2asc(dn_ntohs(*(dn_address *)dn_db->peer->primary_key), peer_buf) : "");
+	}
+	return 0;
+}
+
+static struct seq_operations dn_dev_seq_ops = {
+	.start	= dn_dev_seq_start,
+	.next	= dn_dev_seq_next,
+	.stop	= dn_dev_seq_stop,
+	.show	= dn_dev_seq_show,
+};
+
+static int dn_dev_seq_open(struct inode *inode, struct file *file)
+{
+	return seq_open(file, &dn_dev_seq_ops);
+}
+
+static struct file_operations dn_dev_seq_fops = {
+	.owner	 = THIS_MODULE,
+	.open	 = dn_dev_seq_open,
+	.read	 = seq_read,
+	.llseek	 = seq_lseek,
+	.release = seq_release,
+};
+
+#endif /* CONFIG_PROC_FS */
+
+static struct rtnetlink_link dnet_rtnetlink_table[RTM_MAX-RTM_BASE+1] = 
+{
+	 [4] = { .doit   = dn_dev_rtm_newaddr,	},
+	 [5] = { .doit   = dn_dev_rtm_deladdr,	},
+	 [6] = { .dumpit = dn_dev_dump_ifaddr,	},
+
+#ifdef CONFIG_DECNET_ROUTER
+	 [8] = { .doit   = dn_fib_rtm_newroute,	},
+	 [9] = { .doit   = dn_fib_rtm_delroute,	},
+	[10] = { .doit   = dn_cache_getroute, .dumpit = dn_fib_dump, },
+	[16] = { .doit   = dn_fib_rtm_newrule, },
+	[17] = { .doit   = dn_fib_rtm_delrule, },
+	[18] = { .dumpit = dn_fib_dump_rules,  },
+#else
+	[10] = { .doit   = dn_cache_getroute, .dumpit = dn_cache_dump, },
+#endif
+
+};
+
+static int __initdata addr[2];
+module_param_array(addr, int, NULL, 0444);
+MODULE_PARM_DESC(addr, "The DECnet address of this machine: area,node");
+
+void __init dn_dev_init(void)
+{
+        if (addr[0] > 63 || addr[0] < 0) {
+                printk(KERN_ERR "DECnet: Area must be between 0 and 63");
+                return;
+        }
+
+        if (addr[1] > 1023 || addr[1] < 0) {
+                printk(KERN_ERR "DECnet: Node must be between 0 and 1023");
+                return;
+        }
+
+        decnet_address = dn_htons((addr[0] << 10) | addr[1]);
+
+	dn_dev_devices_on();
+
+	rtnetlink_links[PF_DECnet] = dnet_rtnetlink_table;
+
+	proc_net_fops_create("decnet_dev", S_IRUGO, &dn_dev_seq_fops);
+
+#ifdef CONFIG_SYSCTL
+	{
+		int i;
+		for(i = 0; i < DN_DEV_LIST_SIZE; i++)
+			dn_dev_sysctl_register(NULL, &dn_dev_list[i]);
+	}
+#endif /* CONFIG_SYSCTL */
+}
+
+void __exit dn_dev_cleanup(void)
+{
+	rtnetlink_links[PF_DECnet] = NULL;
+
+#ifdef CONFIG_SYSCTL
+	{
+		int i;
+		for(i = 0; i < DN_DEV_LIST_SIZE; i++)
+			dn_dev_sysctl_unregister(&dn_dev_list[i]);
+	}
+#endif /* CONFIG_SYSCTL */
+
+	proc_net_remove("decnet_dev");
+
+	dn_dev_devices_off();
+}
diff --git a/net/decnet/dn_fib.c b/net/decnet/dn_fib.c
new file mode 100644
index 0000000..9934b25
--- /dev/null
+++ b/net/decnet/dn_fib.c
@@ -0,0 +1,802 @@
+/*
+ * DECnet       An implementation of the DECnet protocol suite for the LINUX
+ *              operating system.  DECnet is implemented using the  BSD Socket
+ *              interface as the means of communication with the user level.
+ *
+ *              DECnet Routing Forwarding Information Base (Glue/Info List)
+ *
+ * Author:      Steve Whitehouse <SteveW@ACM.org>
+ *
+ *
+ * Changes:
+ *              Alexey Kuznetsov : SMP locking changes
+ *              Steve Whitehouse : Rewrote it... Well to be more correct, I
+ *                                 copied most of it from the ipv4 fib code.
+ *              Steve Whitehouse : Updated it in style and fixed a few bugs
+ *                                 which were fixed in the ipv4 code since
+ *                                 this code was copied from it.
+ *
+ */
+#include <linux/config.h>
+#include <linux/string.h>
+#include <linux/net.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/init.h>
+#include <linux/skbuff.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <linux/proc_fs.h>
+#include <linux/netdevice.h>
+#include <linux/timer.h>
+#include <linux/spinlock.h>
+#include <asm/atomic.h>
+#include <asm/uaccess.h>
+#include <net/neighbour.h>
+#include <net/dst.h>
+#include <net/flow.h>
+#include <net/dn.h>
+#include <net/dn_route.h>
+#include <net/dn_fib.h>
+#include <net/dn_neigh.h>
+#include <net/dn_dev.h>
+
+#define RT_MIN_TABLE 1
+
+#define for_fib_info() { struct dn_fib_info *fi;\
+	for(fi = dn_fib_info_list; fi; fi = fi->fib_next)
+#define endfor_fib_info() }
+
+#define for_nexthops(fi) { int nhsel; const struct dn_fib_nh *nh;\
+	for(nhsel = 0, nh = (fi)->fib_nh; nhsel < (fi)->fib_nhs; nh++, nhsel++)
+
+#define change_nexthops(fi) { int nhsel; struct dn_fib_nh *nh;\
+	for(nhsel = 0, nh = (struct dn_fib_nh *)((fi)->fib_nh); nhsel < (fi)->fib_nhs; nh++, nhsel++)
+
+#define endfor_nexthops(fi) }
+
+extern int dn_cache_dump(struct sk_buff *skb, struct netlink_callback *cb);
+
+static DEFINE_SPINLOCK(dn_fib_multipath_lock);
+static struct dn_fib_info *dn_fib_info_list;
+static DEFINE_RWLOCK(dn_fib_info_lock);
+
+static struct
+{
+	int error;
+	u8 scope;
+} dn_fib_props[RTA_MAX+1] = {
+	[RTN_UNSPEC] =      { .error = 0,       .scope = RT_SCOPE_NOWHERE },
+	[RTN_UNICAST] =     { .error = 0,       .scope = RT_SCOPE_UNIVERSE },
+	[RTN_LOCAL] =       { .error = 0,       .scope = RT_SCOPE_HOST },
+	[RTN_BROADCAST] =   { .error = -EINVAL, .scope = RT_SCOPE_NOWHERE },
+	[RTN_ANYCAST] =     { .error = -EINVAL, .scope = RT_SCOPE_NOWHERE },
+	[RTN_MULTICAST] =   { .error = -EINVAL, .scope = RT_SCOPE_NOWHERE },
+	[RTN_BLACKHOLE] =   { .error = -EINVAL, .scope = RT_SCOPE_UNIVERSE },
+	[RTN_UNREACHABLE] = { .error = -EHOSTUNREACH, .scope = RT_SCOPE_UNIVERSE },
+	[RTN_PROHIBIT] =    { .error = -EACCES, .scope = RT_SCOPE_UNIVERSE },
+	[RTN_THROW] =       { .error = -EAGAIN, .scope = RT_SCOPE_UNIVERSE },
+	[RTN_NAT] =         { .error = 0,       .scope = RT_SCOPE_NOWHERE },
+	[RTN_XRESOLVE] =    { .error = -EINVAL, .scope = RT_SCOPE_NOWHERE },
+};
+
+void dn_fib_free_info(struct dn_fib_info *fi)
+{
+	if (fi->fib_dead == 0) {
+		printk(KERN_DEBUG "DECnet: BUG! Attempt to free alive dn_fib_info\n");
+		return;
+	}
+
+	change_nexthops(fi) {
+		if (nh->nh_dev)
+			dev_put(nh->nh_dev);
+		nh->nh_dev = NULL;
+	} endfor_nexthops(fi);
+	kfree(fi);
+}
+
+void dn_fib_release_info(struct dn_fib_info *fi)
+{
+	write_lock(&dn_fib_info_lock);
+	if (fi && --fi->fib_treeref == 0) {
+		if (fi->fib_next)
+			fi->fib_next->fib_prev = fi->fib_prev;
+		if (fi->fib_prev)
+			fi->fib_prev->fib_next = fi->fib_next;
+		if (fi == dn_fib_info_list)
+			dn_fib_info_list = fi->fib_next;
+		fi->fib_dead = 1;
+		dn_fib_info_put(fi);
+	}
+	write_unlock(&dn_fib_info_lock);
+}
+
+static inline int dn_fib_nh_comp(const struct dn_fib_info *fi, const struct dn_fib_info *ofi)
+{
+	const struct dn_fib_nh *onh = ofi->fib_nh;
+
+	for_nexthops(fi) {
+		if (nh->nh_oif != onh->nh_oif ||
+			nh->nh_gw != onh->nh_gw ||
+			nh->nh_scope != onh->nh_scope ||
+			nh->nh_weight != onh->nh_weight ||
+			((nh->nh_flags^onh->nh_flags)&~RTNH_F_DEAD))
+				return -1;
+		onh++;
+	} endfor_nexthops(fi);
+	return 0;
+}
+
+static inline struct dn_fib_info *dn_fib_find_info(const struct dn_fib_info *nfi)
+{
+	for_fib_info() {
+		if (fi->fib_nhs != nfi->fib_nhs)
+			continue;
+		if (nfi->fib_protocol == fi->fib_protocol &&
+			nfi->fib_prefsrc == fi->fib_prefsrc &&
+			nfi->fib_priority == fi->fib_priority &&
+			memcmp(nfi->fib_metrics, fi->fib_metrics, sizeof(fi->fib_metrics)) == 0 &&
+			((nfi->fib_flags^fi->fib_flags)&~RTNH_F_DEAD) == 0 &&
+			(nfi->fib_nhs == 0 || dn_fib_nh_comp(fi, nfi) == 0))
+				return fi;
+	} endfor_fib_info();
+	return NULL;
+}
+
+u16 dn_fib_get_attr16(struct rtattr *attr, int attrlen, int type)
+{
+	while(RTA_OK(attr,attrlen)) {
+		if (attr->rta_type == type)
+			return *(u16*)RTA_DATA(attr);
+		attr = RTA_NEXT(attr, attrlen);
+	}
+
+	return 0;
+}
+
+static int dn_fib_count_nhs(struct rtattr *rta)
+{
+	int nhs = 0;
+	struct rtnexthop *nhp = RTA_DATA(rta);
+	int nhlen = RTA_PAYLOAD(rta);
+
+	while(nhlen >= (int)sizeof(struct rtnexthop)) {
+		if ((nhlen -= nhp->rtnh_len) < 0)
+			return 0;
+		nhs++;
+		nhp = RTNH_NEXT(nhp);
+	}
+
+	return nhs;
+}
+
+static int dn_fib_get_nhs(struct dn_fib_info *fi, const struct rtattr *rta, const struct rtmsg *r)
+{
+	struct rtnexthop *nhp = RTA_DATA(rta);
+	int nhlen = RTA_PAYLOAD(rta);
+
+	change_nexthops(fi) {
+		int attrlen = nhlen - sizeof(struct rtnexthop);
+		if (attrlen < 0 || (nhlen -= nhp->rtnh_len) < 0)
+			return -EINVAL;
+
+		nh->nh_flags  = (r->rtm_flags&~0xFF) | nhp->rtnh_flags;
+		nh->nh_oif    = nhp->rtnh_ifindex;
+		nh->nh_weight = nhp->rtnh_hops + 1;
+
+		if (attrlen) {
+			nh->nh_gw = dn_fib_get_attr16(RTNH_DATA(nhp), attrlen, RTA_GATEWAY);
+		}
+		nhp = RTNH_NEXT(nhp);
+	} endfor_nexthops(fi);
+
+	return 0;
+}
+
+
+static int dn_fib_check_nh(const struct rtmsg *r, struct dn_fib_info *fi, struct dn_fib_nh *nh)
+{
+	int err;
+
+	if (nh->nh_gw) {
+		struct flowi fl;
+		struct dn_fib_res res;
+
+		memset(&fl, 0, sizeof(fl));
+
+		if (nh->nh_flags&RTNH_F_ONLINK) {
+			struct net_device *dev;
+
+			if (r->rtm_scope >= RT_SCOPE_LINK)
+				return -EINVAL;
+			if (dnet_addr_type(nh->nh_gw) != RTN_UNICAST)
+				return -EINVAL;
+			if ((dev = __dev_get_by_index(nh->nh_oif)) == NULL)
+				return -ENODEV;
+			if (!(dev->flags&IFF_UP))
+				return -ENETDOWN;
+			nh->nh_dev = dev;
+			dev_hold(dev);
+			nh->nh_scope = RT_SCOPE_LINK;
+			return 0;
+		}
+
+		memset(&fl, 0, sizeof(fl));
+		fl.fld_dst = nh->nh_gw;
+		fl.oif = nh->nh_oif;
+		fl.fld_scope = r->rtm_scope + 1;
+
+		if (fl.fld_scope < RT_SCOPE_LINK)
+			fl.fld_scope = RT_SCOPE_LINK;
+
+		if ((err = dn_fib_lookup(&fl, &res)) != 0)
+			return err;
+
+		err = -EINVAL;
+		if (res.type != RTN_UNICAST && res.type != RTN_LOCAL)
+			goto out;
+		nh->nh_scope = res.scope;
+		nh->nh_oif = DN_FIB_RES_OIF(res);
+		nh->nh_dev = DN_FIB_RES_DEV(res);
+		if (nh->nh_dev == NULL)
+			goto out;
+		dev_hold(nh->nh_dev);
+		err = -ENETDOWN;
+		if (!(nh->nh_dev->flags & IFF_UP))
+			goto out;
+		err = 0;
+out:
+		dn_fib_res_put(&res);
+		return err;
+	} else {
+		struct net_device *dev;
+
+		if (nh->nh_flags&(RTNH_F_PERVASIVE|RTNH_F_ONLINK))
+			return -EINVAL;
+
+		dev = __dev_get_by_index(nh->nh_oif);
+		if (dev == NULL || dev->dn_ptr == NULL)
+			return -ENODEV;
+		if (!(dev->flags&IFF_UP))
+			return -ENETDOWN;
+		nh->nh_dev = dev;
+		dev_hold(nh->nh_dev);
+		nh->nh_scope = RT_SCOPE_HOST;
+	}
+
+	return 0;
+}
+
+
+struct dn_fib_info *dn_fib_create_info(const struct rtmsg *r, struct dn_kern_rta *rta, const struct nlmsghdr *nlh, int *errp)
+{
+	int err;
+	struct dn_fib_info *fi = NULL;
+	struct dn_fib_info *ofi;
+	int nhs = 1;
+
+	if (dn_fib_props[r->rtm_type].scope > r->rtm_scope)
+		goto err_inval;
+
+	if (rta->rta_mp) {
+		nhs = dn_fib_count_nhs(rta->rta_mp);
+		if (nhs == 0)
+			goto err_inval;
+	}
+
+	fi = kmalloc(sizeof(*fi)+nhs*sizeof(struct dn_fib_nh), GFP_KERNEL);
+	err = -ENOBUFS;
+	if (fi == NULL)
+		goto failure;
+	memset(fi, 0, sizeof(*fi)+nhs*sizeof(struct dn_fib_nh));
+
+	fi->fib_protocol = r->rtm_protocol;
+	fi->fib_nhs = nhs;
+	fi->fib_flags = r->rtm_flags;
+	if (rta->rta_priority)
+		fi->fib_priority = *rta->rta_priority;
+	if (rta->rta_mx) {
+		int attrlen = RTA_PAYLOAD(rta->rta_mx);
+		struct rtattr *attr = RTA_DATA(rta->rta_mx);
+
+		while(RTA_OK(attr, attrlen)) {
+			unsigned flavour = attr->rta_type;
+			if (flavour) {
+				if (flavour > RTAX_MAX)
+					goto err_inval;
+				fi->fib_metrics[flavour-1] = *(unsigned*)RTA_DATA(attr);
+			}
+			attr = RTA_NEXT(attr, attrlen);
+		}
+	}
+	if (rta->rta_prefsrc)
+		memcpy(&fi->fib_prefsrc, rta->rta_prefsrc, 2);
+
+	if (rta->rta_mp) {
+		if ((err = dn_fib_get_nhs(fi, rta->rta_mp, r)) != 0)
+			goto failure;
+		if (rta->rta_oif && fi->fib_nh->nh_oif != *rta->rta_oif)
+			goto err_inval;
+		if (rta->rta_gw && memcmp(&fi->fib_nh->nh_gw, rta->rta_gw, 2))
+			goto err_inval;
+	} else {
+		struct dn_fib_nh *nh = fi->fib_nh;
+		if (rta->rta_oif)
+			nh->nh_oif = *rta->rta_oif;
+		if (rta->rta_gw)
+			memcpy(&nh->nh_gw, rta->rta_gw, 2);
+		nh->nh_flags = r->rtm_flags;
+		nh->nh_weight = 1;
+	}
+
+	if (r->rtm_type == RTN_NAT) {
+		if (rta->rta_gw == NULL || nhs != 1 || rta->rta_oif)
+			goto err_inval;
+		memcpy(&fi->fib_nh->nh_gw, rta->rta_gw, 2);
+		goto link_it;
+	}
+
+	if (dn_fib_props[r->rtm_type].error) {
+		if (rta->rta_gw || rta->rta_oif || rta->rta_mp)
+			goto err_inval;
+		goto link_it;
+	}
+
+	if (r->rtm_scope > RT_SCOPE_HOST)
+		goto err_inval;
+
+	if (r->rtm_scope == RT_SCOPE_HOST) {
+		struct dn_fib_nh *nh = fi->fib_nh;
+
+		/* Local address is added */
+		if (nhs != 1 || nh->nh_gw)
+			goto err_inval;
+		nh->nh_scope = RT_SCOPE_NOWHERE;
+		nh->nh_dev = dev_get_by_index(fi->fib_nh->nh_oif);
+		err = -ENODEV;
+		if (nh->nh_dev == NULL)
+			goto failure;
+	} else {
+		change_nexthops(fi) {
+			if ((err = dn_fib_check_nh(r, fi, nh)) != 0)
+				goto failure;
+		} endfor_nexthops(fi)
+	}
+
+	if (fi->fib_prefsrc) {
+		if (r->rtm_type != RTN_LOCAL || rta->rta_dst == NULL ||
+		    memcmp(&fi->fib_prefsrc, rta->rta_dst, 2))
+			if (dnet_addr_type(fi->fib_prefsrc) != RTN_LOCAL)
+				goto err_inval;
+	}
+
+link_it:
+	if ((ofi = dn_fib_find_info(fi)) != NULL) {
+		fi->fib_dead = 1;
+		dn_fib_free_info(fi);
+		ofi->fib_treeref++;
+		return ofi;
+	}
+
+	fi->fib_treeref++;
+	atomic_inc(&fi->fib_clntref);
+	write_lock(&dn_fib_info_lock);
+	fi->fib_next = dn_fib_info_list;
+	fi->fib_prev = NULL;
+	if (dn_fib_info_list)
+		dn_fib_info_list->fib_prev = fi;
+	dn_fib_info_list = fi;
+	write_unlock(&dn_fib_info_lock);
+	return fi;
+
+err_inval:
+	err = -EINVAL;
+
+failure:
+	*errp = err;
+	if (fi) {
+		fi->fib_dead = 1;
+		dn_fib_free_info(fi);
+	}
+
+	return NULL;
+}
+
+int dn_fib_semantic_match(int type, struct dn_fib_info *fi, const struct flowi *fl, struct dn_fib_res *res)
+{
+	int err = dn_fib_props[type].error;
+
+	if (err == 0) {
+		if (fi->fib_flags & RTNH_F_DEAD)
+			return 1;
+
+		res->fi = fi;
+
+		switch(type) {
+			case RTN_NAT:
+				DN_FIB_RES_RESET(*res);
+				atomic_inc(&fi->fib_clntref);
+				return 0;
+			case RTN_UNICAST:
+			case RTN_LOCAL:
+				for_nexthops(fi) {
+					if (nh->nh_flags & RTNH_F_DEAD)
+						continue;
+					if (!fl->oif || fl->oif == nh->nh_oif)
+						break;
+				}
+				if (nhsel < fi->fib_nhs) {
+					res->nh_sel = nhsel;
+					atomic_inc(&fi->fib_clntref);
+					return 0;
+				}
+				endfor_nexthops(fi);
+				res->fi = NULL;
+				return 1;
+			default:
+				if (net_ratelimit())
+					 printk("DECnet: impossible routing event : dn_fib_semantic_match type=%d\n", type);
+				res->fi = NULL;
+				return -EINVAL;
+		}
+	}
+	return err;
+}
+
+void dn_fib_select_multipath(const struct flowi *fl, struct dn_fib_res *res)
+{
+	struct dn_fib_info *fi = res->fi;
+	int w;
+
+	spin_lock_bh(&dn_fib_multipath_lock);
+	if (fi->fib_power <= 0) {
+		int power = 0;
+		change_nexthops(fi) {
+			if (!(nh->nh_flags&RTNH_F_DEAD)) {
+				power += nh->nh_weight;
+				nh->nh_power = nh->nh_weight;
+			}
+		} endfor_nexthops(fi);
+		fi->fib_power = power;
+		if (power < 0) {
+			spin_unlock_bh(&dn_fib_multipath_lock);
+			res->nh_sel = 0;
+			return;
+		}
+	}
+
+	w = jiffies % fi->fib_power;
+
+	change_nexthops(fi) {
+		if (!(nh->nh_flags&RTNH_F_DEAD) && nh->nh_power) {
+			if ((w -= nh->nh_power) <= 0) {
+				nh->nh_power--;
+				fi->fib_power--;
+				res->nh_sel = nhsel;
+				spin_unlock_bh(&dn_fib_multipath_lock);
+				return;
+			}
+		}
+	} endfor_nexthops(fi);
+	res->nh_sel = 0;
+	spin_unlock_bh(&dn_fib_multipath_lock);
+}
+
+
+static int dn_fib_check_attr(struct rtmsg *r, struct rtattr **rta)
+{
+	int i;
+
+	for(i = 1; i <= RTA_MAX; i++) {
+		struct rtattr *attr = rta[i-1];
+		if (attr) {
+			if (RTA_PAYLOAD(attr) < 4 && RTA_PAYLOAD(attr) != 2)
+				return -EINVAL;
+			if (i != RTA_MULTIPATH && i != RTA_METRICS)
+				rta[i-1] = (struct rtattr *)RTA_DATA(attr);
+		}
+	}
+
+	return 0;
+}
+
+int dn_fib_rtm_delroute(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
+{
+	struct dn_fib_table *tb;
+	struct rtattr **rta = arg;
+	struct rtmsg *r = NLMSG_DATA(nlh);
+
+	if (dn_fib_check_attr(r, rta))
+		return -EINVAL;
+
+	tb = dn_fib_get_table(r->rtm_table, 0);
+	if (tb)
+		return tb->delete(tb, r, (struct dn_kern_rta *)rta, nlh, &NETLINK_CB(skb));
+
+	return -ESRCH;
+}
+
+int dn_fib_rtm_newroute(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
+{
+	struct dn_fib_table *tb;
+	struct rtattr **rta = arg;
+	struct rtmsg *r = NLMSG_DATA(nlh);
+
+	if (dn_fib_check_attr(r, rta))
+		return -EINVAL;
+
+	tb = dn_fib_get_table(r->rtm_table, 1);
+	if (tb) 
+		return tb->insert(tb, r, (struct dn_kern_rta *)rta, nlh, &NETLINK_CB(skb));
+
+	return -ENOBUFS;
+}
+
+
+int dn_fib_dump(struct sk_buff *skb, struct netlink_callback *cb)
+{
+	int t;
+	int s_t;
+	struct dn_fib_table *tb;
+
+	if (NLMSG_PAYLOAD(cb->nlh, 0) >= sizeof(struct rtmsg) &&
+		((struct rtmsg *)NLMSG_DATA(cb->nlh))->rtm_flags&RTM_F_CLONED)
+			return dn_cache_dump(skb, cb);
+
+	s_t = cb->args[0];
+	if (s_t == 0)
+		s_t = cb->args[0] = RT_MIN_TABLE;
+
+	for(t = s_t; t <= RT_TABLE_MAX; t++) {
+		if (t < s_t)
+			continue;
+		if (t > s_t)
+			memset(&cb->args[1], 0, sizeof(cb->args)-sizeof(int));
+		tb = dn_fib_get_table(t, 0);
+		if (tb == NULL)
+			continue;
+		if (tb->dump(tb, skb, cb) < 0)
+			break;
+	}
+
+	cb->args[0] = t;
+
+	return skb->len;
+}
+
+static void fib_magic(int cmd, int type, __u16 dst, int dst_len, struct dn_ifaddr *ifa)
+{
+	struct dn_fib_table *tb;
+	struct {
+		struct nlmsghdr nlh;
+		struct rtmsg rtm;
+	} req;
+	struct dn_kern_rta rta;
+
+	memset(&req.rtm, 0, sizeof(req.rtm));
+	memset(&rta, 0, sizeof(rta));
+
+	if (type == RTN_UNICAST)
+		tb = dn_fib_get_table(RT_MIN_TABLE, 1);
+	else
+		tb = dn_fib_get_table(RT_TABLE_LOCAL, 1);
+
+	if (tb == NULL)
+		return;
+
+	req.nlh.nlmsg_len = sizeof(req);
+	req.nlh.nlmsg_type = cmd;
+	req.nlh.nlmsg_flags = NLM_F_REQUEST|NLM_F_CREATE|NLM_F_APPEND;
+	req.nlh.nlmsg_pid = 0;
+	req.nlh.nlmsg_seq = 0;
+
+	req.rtm.rtm_dst_len = dst_len;
+	req.rtm.rtm_table = tb->n;
+	req.rtm.rtm_protocol = RTPROT_KERNEL;
+	req.rtm.rtm_scope = (type != RTN_LOCAL ? RT_SCOPE_LINK : RT_SCOPE_HOST);
+	req.rtm.rtm_type = type;
+
+	rta.rta_dst = &dst;
+	rta.rta_prefsrc = &ifa->ifa_local;
+	rta.rta_oif = &ifa->ifa_dev->dev->ifindex;
+
+	if (cmd == RTM_NEWROUTE)
+		tb->insert(tb, &req.rtm, &rta, &req.nlh, NULL);
+	else
+		tb->delete(tb, &req.rtm, &rta, &req.nlh, NULL);
+}
+
+static void dn_fib_add_ifaddr(struct dn_ifaddr *ifa)
+{
+
+	fib_magic(RTM_NEWROUTE, RTN_LOCAL, ifa->ifa_local, 16, ifa);
+
+#if 0
+	if (!(dev->flags&IFF_UP))
+		return;
+	/* In the future, we will want to add default routes here */
+
+#endif
+}
+
+static void dn_fib_del_ifaddr(struct dn_ifaddr *ifa)
+{
+	int found_it = 0;
+	struct net_device *dev;
+	struct dn_dev *dn_db;
+	struct dn_ifaddr *ifa2;
+
+	ASSERT_RTNL();
+
+	/* Scan device list */
+	read_lock(&dev_base_lock);
+	for(dev = dev_base; dev; dev = dev->next) {
+		dn_db = dev->dn_ptr;
+		if (dn_db == NULL)
+			continue;
+		for(ifa2 = dn_db->ifa_list; ifa2; ifa2 = ifa2->ifa_next) {
+			if (ifa2->ifa_local == ifa->ifa_local) {
+				found_it = 1;
+				break;
+			}
+		}
+	}
+	read_unlock(&dev_base_lock);
+
+	if (found_it == 0) {
+		fib_magic(RTM_DELROUTE, RTN_LOCAL, ifa->ifa_local, 16, ifa);
+
+		if (dnet_addr_type(ifa->ifa_local) != RTN_LOCAL) {
+			if (dn_fib_sync_down(ifa->ifa_local, NULL, 0))
+				dn_fib_flush();
+		}
+	}
+}
+
+static void dn_fib_disable_addr(struct net_device *dev, int force)
+{
+	if (dn_fib_sync_down(0, dev, force))
+		dn_fib_flush();
+	dn_rt_cache_flush(0);
+	neigh_ifdown(&dn_neigh_table, dev);
+}
+
+static int dn_fib_dnaddr_event(struct notifier_block *this, unsigned long event, void *ptr)
+{
+	struct dn_ifaddr *ifa = (struct dn_ifaddr *)ptr;
+
+	switch(event) {
+		case NETDEV_UP:
+			dn_fib_add_ifaddr(ifa);
+			dn_fib_sync_up(ifa->ifa_dev->dev);
+			dn_rt_cache_flush(-1);
+			break;
+		case NETDEV_DOWN:
+			dn_fib_del_ifaddr(ifa);
+			if (ifa->ifa_dev && ifa->ifa_dev->ifa_list == NULL) {
+				dn_fib_disable_addr(ifa->ifa_dev->dev, 1);
+			} else {
+				dn_rt_cache_flush(-1);
+			}
+			break;
+	}
+	return NOTIFY_DONE;
+}
+
+int dn_fib_sync_down(dn_address local, struct net_device *dev, int force)
+{
+        int ret = 0;
+        int scope = RT_SCOPE_NOWHERE;
+
+        if (force)
+                scope = -1;
+
+        for_fib_info() {
+                /* 
+                 * This makes no sense for DECnet.... we will almost
+                 * certainly have more than one local address the same
+                 * over all our interfaces. It needs thinking about
+                 * some more.
+                 */
+                if (local && fi->fib_prefsrc == local) {
+                        fi->fib_flags |= RTNH_F_DEAD;
+                        ret++;
+                } else if (dev && fi->fib_nhs) {
+                        int dead = 0;
+
+                        change_nexthops(fi) {
+                                if (nh->nh_flags&RTNH_F_DEAD)
+                                        dead++;
+                                else if (nh->nh_dev == dev &&
+                                                nh->nh_scope != scope) {
+					spin_lock_bh(&dn_fib_multipath_lock);
+                                        nh->nh_flags |= RTNH_F_DEAD;
+                                        fi->fib_power -= nh->nh_power;
+                                        nh->nh_power = 0;
+					spin_unlock_bh(&dn_fib_multipath_lock);
+                                        dead++;
+                                }
+                        } endfor_nexthops(fi)
+                        if (dead == fi->fib_nhs) {
+                                fi->fib_flags |= RTNH_F_DEAD;
+                                ret++;
+                        }
+                }
+        } endfor_fib_info();
+        return ret;
+}
+
+
+int dn_fib_sync_up(struct net_device *dev)
+{
+        int ret = 0;
+
+        if (!(dev->flags&IFF_UP))
+                return 0;
+
+        for_fib_info() {
+                int alive = 0;
+
+                change_nexthops(fi) {
+                        if (!(nh->nh_flags&RTNH_F_DEAD)) {
+                                alive++;
+                                continue;
+                        }
+                        if (nh->nh_dev == NULL || !(nh->nh_dev->flags&IFF_UP))
+                                continue;
+                        if (nh->nh_dev != dev || dev->dn_ptr == NULL)
+                                continue;
+                        alive++;
+			spin_lock_bh(&dn_fib_multipath_lock);
+                        nh->nh_power = 0;
+                        nh->nh_flags &= ~RTNH_F_DEAD;
+			spin_unlock_bh(&dn_fib_multipath_lock);
+                } endfor_nexthops(fi);
+
+                if (alive > 0) {
+                        fi->fib_flags &= ~RTNH_F_DEAD;
+                        ret++;
+                }
+        } endfor_fib_info();
+        return ret;
+}
+
+void dn_fib_flush(void)
+{
+        int flushed = 0;
+        struct dn_fib_table *tb;
+        int id;
+
+        for(id = RT_TABLE_MAX; id > 0; id--) {
+                if ((tb = dn_fib_get_table(id, 0)) == NULL)
+                        continue;
+                flushed += tb->flush(tb);
+        }
+
+        if (flushed)
+                dn_rt_cache_flush(-1);
+}
+
+static struct notifier_block dn_fib_dnaddr_notifier = {
+	.notifier_call = dn_fib_dnaddr_event,
+};
+
+void __exit dn_fib_cleanup(void)
+{
+	dn_fib_table_cleanup();
+	dn_fib_rules_cleanup();
+
+	unregister_dnaddr_notifier(&dn_fib_dnaddr_notifier);
+}
+
+
+void __init dn_fib_init(void)
+{
+
+	dn_fib_table_init();
+	dn_fib_rules_init();
+
+	register_dnaddr_notifier(&dn_fib_dnaddr_notifier);
+}
+
+
diff --git a/net/decnet/dn_neigh.c b/net/decnet/dn_neigh.c
new file mode 100644
index 0000000..f6dfe96
--- /dev/null
+++ b/net/decnet/dn_neigh.c
@@ -0,0 +1,627 @@
+/*
+ * DECnet       An implementation of the DECnet protocol suite for the LINUX
+ *              operating system.  DECnet is implemented using the  BSD Socket
+ *              interface as the means of communication with the user level.
+ *
+ *              DECnet Neighbour Functions (Adjacency Database and 
+ *                                                        On-Ethernet Cache)
+ *
+ * Author:      Steve Whitehouse <SteveW@ACM.org>
+ *
+ *
+ * Changes:
+ *     Steve Whitehouse     : Fixed router listing routine
+ *     Steve Whitehouse     : Added error_report functions
+ *     Steve Whitehouse     : Added default router detection
+ *     Steve Whitehouse     : Hop counts in outgoing messages
+ *     Steve Whitehouse     : Fixed src/dst in outgoing messages so
+ *                            forwarding now stands a good chance of
+ *                            working.
+ *     Steve Whitehouse     : Fixed neighbour states (for now anyway).
+ *     Steve Whitehouse     : Made error_report functions dummies. This
+ *                            is not the right place to return skbs.
+ *     Steve Whitehouse     : Convert to seq_file
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/net.h>
+#include <linux/module.h>
+#include <linux/socket.h>
+#include <linux/if_arp.h>
+#include <linux/if_ether.h>
+#include <linux/init.h>
+#include <linux/proc_fs.h>
+#include <linux/string.h>
+#include <linux/netfilter_decnet.h>
+#include <linux/spinlock.h>
+#include <linux/seq_file.h>
+#include <linux/rcupdate.h>
+#include <linux/jhash.h>
+#include <asm/atomic.h>
+#include <net/neighbour.h>
+#include <net/dst.h>
+#include <net/flow.h>
+#include <net/dn.h>
+#include <net/dn_dev.h>
+#include <net/dn_neigh.h>
+#include <net/dn_route.h>
+
+static u32 dn_neigh_hash(const void *pkey, const struct net_device *dev);
+static int dn_neigh_construct(struct neighbour *);
+static void dn_long_error_report(struct neighbour *, struct sk_buff *);
+static void dn_short_error_report(struct neighbour *, struct sk_buff *);
+static int dn_long_output(struct sk_buff *);
+static int dn_short_output(struct sk_buff *);
+static int dn_phase3_output(struct sk_buff *);
+
+
+/*
+ * For talking to broadcast devices: Ethernet & PPP
+ */
+static struct neigh_ops dn_long_ops = {
+	.family =		AF_DECnet,
+	.error_report =		dn_long_error_report,
+	.output =		dn_long_output,
+	.connected_output =	dn_long_output,
+	.hh_output =		dev_queue_xmit,
+	.queue_xmit =		dev_queue_xmit,
+};
+
+/*
+ * For talking to pointopoint and multidrop devices: DDCMP and X.25
+ */
+static struct neigh_ops dn_short_ops = {
+	.family =		AF_DECnet,
+	.error_report =		dn_short_error_report,
+	.output =		dn_short_output,
+	.connected_output =	dn_short_output,
+	.hh_output =		dev_queue_xmit,
+	.queue_xmit =		dev_queue_xmit,
+};
+
+/*
+ * For talking to DECnet phase III nodes
+ */
+static struct neigh_ops dn_phase3_ops = {
+	.family =		AF_DECnet,
+	.error_report =		dn_short_error_report, /* Can use short version here */
+	.output =		dn_phase3_output,
+	.connected_output =	dn_phase3_output,
+	.hh_output =		dev_queue_xmit,
+	.queue_xmit =		dev_queue_xmit
+};
+
+struct neigh_table dn_neigh_table = {
+	.family =			PF_DECnet,
+	.entry_size =			sizeof(struct dn_neigh),
+	.key_len =			sizeof(dn_address),
+	.hash =				dn_neigh_hash,
+	.constructor =			dn_neigh_construct,
+	.id =				"dn_neigh_cache",
+	.parms ={
+		.tbl =			&dn_neigh_table,
+		.entries =		0,
+		.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 =	0,
+		.app_probes =		0,
+		.mcast_probes =	0,
+		.anycast_delay =	0,
+		.proxy_delay =		0,
+		.proxy_qlen =		0,
+		.locktime =		1 * HZ,
+	},
+	.gc_interval =			30 * HZ,
+	.gc_thresh1 =			128,
+	.gc_thresh2 =			512,
+	.gc_thresh3 =			1024,
+};
+
+static u32 dn_neigh_hash(const void *pkey, const struct net_device *dev)
+{
+	return jhash_2words(*(dn_address *)pkey, 0, dn_neigh_table.hash_rnd);
+}
+
+static int dn_neigh_construct(struct neighbour *neigh)
+{
+	struct net_device *dev = neigh->dev;
+	struct dn_neigh *dn = (struct dn_neigh *)neigh;
+	struct dn_dev *dn_db;
+	struct neigh_parms *parms;
+
+	rcu_read_lock();
+	dn_db = rcu_dereference(dev->dn_ptr);
+	if (dn_db == NULL) {
+		rcu_read_unlock();
+		return -EINVAL;
+	}
+
+	parms = dn_db->neigh_parms;
+	if (!parms) {
+		rcu_read_unlock();
+		return -EINVAL;
+	}
+
+	__neigh_parms_put(neigh->parms);
+	neigh->parms = neigh_parms_clone(parms);
+	rcu_read_unlock();
+
+	if (dn_db->use_long)
+		neigh->ops = &dn_long_ops;
+	else
+		neigh->ops = &dn_short_ops;
+
+	if (dn->flags & DN_NDFLAG_P3)
+		neigh->ops = &dn_phase3_ops;
+
+	neigh->nud_state = NUD_NOARP;
+	neigh->output = neigh->ops->connected_output;
+
+	if ((dev->type == ARPHRD_IPGRE) || (dev->flags & IFF_POINTOPOINT))
+		memcpy(neigh->ha, dev->broadcast, dev->addr_len);
+	else if ((dev->type == ARPHRD_ETHER) || (dev->type == ARPHRD_LOOPBACK))
+		dn_dn2eth(neigh->ha, dn->addr);
+	else {
+		if (net_ratelimit())
+			printk(KERN_DEBUG "Trying to create neigh for hw %d\n",  dev->type);
+		return -EINVAL;
+	}
+
+	/*
+	 * Make an estimate of the remote block size by assuming that its
+	 * two less then the device mtu, which it true for ethernet (and
+	 * other things which support long format headers) since there is
+	 * an extra length field (of 16 bits) which isn't part of the
+	 * ethernet headers and which the DECnet specs won't admit is part
+	 * of the DECnet routing headers either.
+	 *
+	 * If we over estimate here its no big deal, the NSP negotiations
+	 * will prevent us from sending packets which are too large for the
+	 * remote node to handle. In any case this figure is normally updated
+	 * by a hello message in most cases.
+	 */
+	dn->blksize = dev->mtu - 2;
+
+	return 0;
+}
+
+static void dn_long_error_report(struct neighbour *neigh, struct sk_buff *skb)
+{
+	printk(KERN_DEBUG "dn_long_error_report: called\n");
+	kfree_skb(skb);
+}
+
+
+static void dn_short_error_report(struct neighbour *neigh, struct sk_buff *skb)
+{
+	printk(KERN_DEBUG "dn_short_error_report: called\n");
+	kfree_skb(skb);
+}
+
+static int dn_neigh_output_packet(struct sk_buff *skb)
+{
+	struct dst_entry *dst = skb->dst;
+	struct dn_route *rt = (struct dn_route *)dst;
+	struct neighbour *neigh = dst->neighbour;
+	struct net_device *dev = neigh->dev;
+	char mac_addr[ETH_ALEN];
+
+	dn_dn2eth(mac_addr, rt->rt_local_src);
+	if (!dev->hard_header || dev->hard_header(skb, dev, ntohs(skb->protocol), neigh->ha, mac_addr, skb->len) >= 0)
+		return neigh->ops->queue_xmit(skb);
+
+	if (net_ratelimit())
+		printk(KERN_DEBUG "dn_neigh_output_packet: oops, can't send packet\n");
+
+	kfree_skb(skb);
+	return -EINVAL;
+}
+
+static int dn_long_output(struct sk_buff *skb)
+{
+	struct dst_entry *dst = skb->dst;
+	struct neighbour *neigh = dst->neighbour;
+	struct net_device *dev = neigh->dev;
+	int headroom = dev->hard_header_len + sizeof(struct dn_long_packet) + 3;
+	unsigned char *data;
+	struct dn_long_packet *lp;
+	struct dn_skb_cb *cb = DN_SKB_CB(skb);
+
+
+	if (skb_headroom(skb) < headroom) {
+		struct sk_buff *skb2 = skb_realloc_headroom(skb, headroom);
+		if (skb2 == NULL) {
+			if (net_ratelimit())
+				printk(KERN_CRIT "dn_long_output: no memory\n");
+			kfree_skb(skb);
+			return -ENOBUFS;
+		}
+		kfree_skb(skb);
+		skb = skb2;
+		if (net_ratelimit())
+			printk(KERN_INFO "dn_long_output: Increasing headroom\n");
+	}
+
+	data = skb_push(skb, sizeof(struct dn_long_packet) + 3);
+	lp = (struct dn_long_packet *)(data+3);
+
+	*((unsigned short *)data) = dn_htons(skb->len - 2);
+	*(data + 2) = 1 | DN_RT_F_PF; /* Padding */
+
+	lp->msgflg   = DN_RT_PKT_LONG|(cb->rt_flags&(DN_RT_F_IE|DN_RT_F_RQR|DN_RT_F_RTS));
+	lp->d_area   = lp->d_subarea = 0;
+	dn_dn2eth(lp->d_id, dn_ntohs(cb->dst));
+	lp->s_area   = lp->s_subarea = 0;
+	dn_dn2eth(lp->s_id, dn_ntohs(cb->src));
+	lp->nl2      = 0;
+	lp->visit_ct = cb->hops & 0x3f;
+	lp->s_class  = 0;
+	lp->pt       = 0;
+
+	skb->nh.raw = skb->data;
+
+	return NF_HOOK(PF_DECnet, NF_DN_POST_ROUTING, skb, NULL, neigh->dev, dn_neigh_output_packet);
+}
+
+static int dn_short_output(struct sk_buff *skb)
+{
+	struct dst_entry *dst = skb->dst;
+	struct neighbour *neigh = dst->neighbour;
+	struct net_device *dev = neigh->dev;
+	int headroom = dev->hard_header_len + sizeof(struct dn_short_packet) + 2;
+	struct dn_short_packet *sp;
+	unsigned char *data;
+	struct dn_skb_cb *cb = DN_SKB_CB(skb);
+
+
+        if (skb_headroom(skb) < headroom) {
+                struct sk_buff *skb2 = skb_realloc_headroom(skb, headroom);
+                if (skb2 == NULL) {
+			if (net_ratelimit())
+                        	printk(KERN_CRIT "dn_short_output: no memory\n");
+                        kfree_skb(skb);
+                        return -ENOBUFS;
+                }
+                kfree_skb(skb);
+                skb = skb2;
+		if (net_ratelimit())
+                	printk(KERN_INFO "dn_short_output: Increasing headroom\n");
+        }
+
+	data = skb_push(skb, sizeof(struct dn_short_packet) + 2);
+	*((unsigned short *)data) = dn_htons(skb->len - 2);
+	sp = (struct dn_short_packet *)(data+2);
+
+	sp->msgflg     = DN_RT_PKT_SHORT|(cb->rt_flags&(DN_RT_F_RQR|DN_RT_F_RTS));
+	sp->dstnode    = cb->dst;
+	sp->srcnode    = cb->src;
+	sp->forward    = cb->hops & 0x3f;
+
+	skb->nh.raw = skb->data;
+
+	return NF_HOOK(PF_DECnet, NF_DN_POST_ROUTING, skb, NULL, neigh->dev, dn_neigh_output_packet);
+}
+
+/*
+ * Phase 3 output is the same is short output, execpt that
+ * it clears the area bits before transmission.
+ */
+static int dn_phase3_output(struct sk_buff *skb)
+{
+	struct dst_entry *dst = skb->dst;
+	struct neighbour *neigh = dst->neighbour;
+	struct net_device *dev = neigh->dev;
+	int headroom = dev->hard_header_len + sizeof(struct dn_short_packet) + 2;
+	struct dn_short_packet *sp;
+	unsigned char *data;
+	struct dn_skb_cb *cb = DN_SKB_CB(skb);
+
+	if (skb_headroom(skb) < headroom) {
+		struct sk_buff *skb2 = skb_realloc_headroom(skb, headroom);
+		if (skb2 == NULL) {
+			if (net_ratelimit())
+				printk(KERN_CRIT "dn_phase3_output: no memory\n");
+			kfree_skb(skb);
+			return -ENOBUFS;
+		}
+		kfree_skb(skb);
+		skb = skb2;
+		if (net_ratelimit())
+			printk(KERN_INFO "dn_phase3_output: Increasing headroom\n");
+	}
+
+	data = skb_push(skb, sizeof(struct dn_short_packet) + 2);
+	*((unsigned short *)data) = dn_htons(skb->len - 2);
+	sp = (struct dn_short_packet *)(data + 2);
+
+	sp->msgflg   = DN_RT_PKT_SHORT|(cb->rt_flags&(DN_RT_F_RQR|DN_RT_F_RTS));
+	sp->dstnode  = cb->dst & dn_htons(0x03ff);
+	sp->srcnode  = cb->src & dn_htons(0x03ff);
+	sp->forward  = cb->hops & 0x3f;
+
+	skb->nh.raw = skb->data;
+
+	return NF_HOOK(PF_DECnet, NF_DN_POST_ROUTING, skb, NULL, neigh->dev, dn_neigh_output_packet);
+}
+
+/*
+ * Unfortunately, the neighbour code uses the device in its hash
+ * function, so we don't get any advantage from it. This function
+ * basically does a neigh_lookup(), but without comparing the device
+ * field. This is required for the On-Ethernet cache
+ */
+
+/*
+ * Pointopoint link receives a hello message
+ */
+void dn_neigh_pointopoint_hello(struct sk_buff *skb)
+{
+	kfree_skb(skb);
+}
+
+/*
+ * Ethernet router hello message received
+ */
+int dn_neigh_router_hello(struct sk_buff *skb)
+{
+	struct rtnode_hello_message *msg = (struct rtnode_hello_message *)skb->data;
+
+	struct neighbour *neigh;
+	struct dn_neigh *dn;
+	struct dn_dev *dn_db;
+	dn_address src;
+
+	src = dn_htons(dn_eth2dn(msg->id));
+
+	neigh = __neigh_lookup(&dn_neigh_table, &src, skb->dev, 1);
+
+	dn = (struct dn_neigh *)neigh;
+
+	if (neigh) {
+		write_lock(&neigh->lock);
+
+		neigh->used = jiffies;
+		dn_db = (struct dn_dev *)neigh->dev->dn_ptr;
+
+		if (!(neigh->nud_state & NUD_PERMANENT)) {
+			neigh->updated = jiffies;
+
+			if (neigh->dev->type == ARPHRD_ETHER)
+				memcpy(neigh->ha, &eth_hdr(skb)->h_source, ETH_ALEN);
+
+			dn->blksize  = dn_ntohs(msg->blksize);
+			dn->priority = msg->priority;
+
+			dn->flags &= ~DN_NDFLAG_P3;
+
+			switch(msg->iinfo & DN_RT_INFO_TYPE) {
+				case DN_RT_INFO_L1RT:
+					dn->flags &=~DN_NDFLAG_R2;
+					dn->flags |= DN_NDFLAG_R1;
+					break;
+				case DN_RT_INFO_L2RT:
+					dn->flags |= DN_NDFLAG_R2;
+			}
+		}
+
+		if (!dn_db->router) {
+			dn_db->router = neigh_clone(neigh);
+		} else {
+			if (msg->priority > ((struct dn_neigh *)dn_db->router)->priority)
+				neigh_release(xchg(&dn_db->router, neigh_clone(neigh)));
+		}
+		write_unlock(&neigh->lock);
+		neigh_release(neigh);
+	}
+
+	kfree_skb(skb);
+	return 0;
+}
+
+/*
+ * Endnode hello message received
+ */
+int dn_neigh_endnode_hello(struct sk_buff *skb)
+{
+	struct endnode_hello_message *msg = (struct endnode_hello_message *)skb->data;
+	struct neighbour *neigh;
+	struct dn_neigh *dn;
+	dn_address src;
+
+	src = dn_htons(dn_eth2dn(msg->id));
+
+	neigh = __neigh_lookup(&dn_neigh_table, &src, skb->dev, 1);
+
+	dn = (struct dn_neigh *)neigh;
+
+	if (neigh) {
+		write_lock(&neigh->lock);
+
+		neigh->used = jiffies;
+
+		if (!(neigh->nud_state & NUD_PERMANENT)) {
+			neigh->updated = jiffies;
+
+			if (neigh->dev->type == ARPHRD_ETHER)
+				memcpy(neigh->ha, &eth_hdr(skb)->h_source, ETH_ALEN);
+			dn->flags   &= ~(DN_NDFLAG_R1 | DN_NDFLAG_R2);
+			dn->blksize  = dn_ntohs(msg->blksize);
+			dn->priority = 0;
+		}
+
+		write_unlock(&neigh->lock);
+		neigh_release(neigh);
+	}
+
+	kfree_skb(skb);
+	return 0;
+}
+
+static char *dn_find_slot(char *base, int max, int priority)
+{
+	int i;
+	unsigned char *min = NULL;
+
+	base += 6; /* skip first id */
+
+	for(i = 0; i < max; i++) {
+		if (!min || (*base < *min))
+			min = base;
+		base += 7; /* find next priority */
+	}
+
+	if (!min)
+		return NULL;
+
+	return (*min < priority) ? (min - 6) : NULL;
+}
+
+struct elist_cb_state {
+	struct net_device *dev;
+	unsigned char *ptr;
+	unsigned char *rs;
+	int t, n;
+};
+
+static void neigh_elist_cb(struct neighbour *neigh, void *_info)
+{
+	struct elist_cb_state *s = _info;
+	struct dn_dev *dn_db;
+	struct dn_neigh *dn;
+
+	if (neigh->dev != s->dev)
+		return;
+
+	dn = (struct dn_neigh *) neigh;
+	if (!(dn->flags & (DN_NDFLAG_R1|DN_NDFLAG_R2)))
+		return;
+
+	dn_db = (struct dn_dev *) s->dev->dn_ptr;
+	if (dn_db->parms.forwarding == 1 && (dn->flags & DN_NDFLAG_R2))
+		return;
+
+	if (s->t == s->n)
+		s->rs = dn_find_slot(s->ptr, s->n, dn->priority);
+	else
+		s->t++;
+	if (s->rs == NULL)
+		return;
+
+	dn_dn2eth(s->rs, dn->addr);
+	s->rs += 6;
+	*(s->rs) = neigh->nud_state & NUD_CONNECTED ? 0x80 : 0x0;
+	*(s->rs) |= dn->priority;
+	s->rs++;
+}
+
+int dn_neigh_elist(struct net_device *dev, unsigned char *ptr, int n)
+{
+	struct elist_cb_state state;
+
+	state.dev = dev;
+	state.t = 0;
+	state.n = n;
+	state.ptr = ptr;
+	state.rs = ptr;
+
+	neigh_for_each(&dn_neigh_table, neigh_elist_cb, &state);
+
+	return state.t;
+}
+
+
+#ifdef CONFIG_PROC_FS
+
+static inline void dn_neigh_format_entry(struct seq_file *seq,
+					 struct neighbour *n)
+{
+	struct dn_neigh *dn = (struct dn_neigh *) n;
+	char buf[DN_ASCBUF_LEN];
+
+	read_lock(&n->lock);
+	seq_printf(seq, "%-7s %s%s%s   %02x    %02d  %07ld %-8s\n",
+		   dn_addr2asc(dn_ntohs(dn->addr), buf),
+		   (dn->flags&DN_NDFLAG_R1) ? "1" : "-",
+		   (dn->flags&DN_NDFLAG_R2) ? "2" : "-",
+		   (dn->flags&DN_NDFLAG_P3) ? "3" : "-",
+		   dn->n.nud_state,
+		   atomic_read(&dn->n.refcnt),
+		   dn->blksize,
+		   (dn->n.dev) ? dn->n.dev->name : "?");
+	read_unlock(&n->lock);
+}
+
+static int dn_neigh_seq_show(struct seq_file *seq, void *v)
+{
+	if (v == SEQ_START_TOKEN) {
+		seq_puts(seq, "Addr    Flags State Use Blksize Dev\n");
+	} else {
+		dn_neigh_format_entry(seq, v);
+	}
+
+	return 0;
+}
+
+static void *dn_neigh_seq_start(struct seq_file *seq, loff_t *pos)
+{
+	return neigh_seq_start(seq, pos, &dn_neigh_table,
+			       NEIGH_SEQ_NEIGH_ONLY);
+}
+
+static struct seq_operations dn_neigh_seq_ops = {
+	.start = dn_neigh_seq_start,
+	.next  = neigh_seq_next,
+	.stop  = neigh_seq_stop,
+	.show  = dn_neigh_seq_show,
+};
+
+static int dn_neigh_seq_open(struct inode *inode, struct file *file)
+{
+	struct seq_file *seq;
+	int rc = -ENOMEM;
+	struct neigh_seq_state *s = kmalloc(sizeof(*s), GFP_KERNEL);
+
+	if (!s)
+		goto out;
+
+	memset(s, 0, sizeof(*s));
+	rc = seq_open(file, &dn_neigh_seq_ops);
+	if (rc)
+		goto out_kfree;
+
+	seq          = file->private_data;
+	seq->private = s;
+	memset(s, 0, sizeof(*s));
+out:
+	return rc;
+out_kfree:
+	kfree(s);
+	goto out;
+}
+
+static struct file_operations dn_neigh_seq_fops = {
+	.owner		= THIS_MODULE,
+	.open		= dn_neigh_seq_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= seq_release_private,
+};
+
+#endif
+
+void __init dn_neigh_init(void)
+{
+	neigh_table_init(&dn_neigh_table);
+	proc_net_fops_create("decnet_neigh", S_IRUGO, &dn_neigh_seq_fops);
+}
+
+void __exit dn_neigh_cleanup(void)
+{
+	proc_net_remove("decnet_neigh");
+	neigh_table_clear(&dn_neigh_table);
+}
diff --git a/net/decnet/dn_nsp_in.c b/net/decnet/dn_nsp_in.c
new file mode 100644
index 0000000..202dbde
--- /dev/null
+++ b/net/decnet/dn_nsp_in.c
@@ -0,0 +1,934 @@
+/*
+ * DECnet       An implementation of the DECnet protocol suite for the LINUX
+ *              operating system.  DECnet is implemented using the  BSD Socket
+ *              interface as the means of communication with the user level.
+ *
+ *              DECnet Network Services Protocol (Input)
+ *
+ * Author:      Eduardo Marcelo Serrat <emserrat@geocities.com>
+ *
+ * Changes:
+ *
+ *    Steve Whitehouse:  Split into dn_nsp_in.c and dn_nsp_out.c from
+ *                       original dn_nsp.c.
+ *    Steve Whitehouse:  Updated to work with my new routing architecture.
+ *    Steve Whitehouse:  Add changes from Eduardo Serrat's patches.
+ *    Steve Whitehouse:  Put all ack handling code in a common routine.
+ *    Steve Whitehouse:  Put other common bits into dn_nsp_rx()
+ *    Steve Whitehouse:  More checks on skb->len to catch bogus packets
+ *                       Fixed various race conditions and possible nasties.
+ *    Steve Whitehouse:  Now handles returned conninit frames.
+ *     David S. Miller:  New socket locking
+ *    Steve Whitehouse:  Fixed lockup when socket filtering was enabled.
+ *         Paul Koning:  Fix to push CC sockets into RUN when acks are
+ *                       received.
+ *    Steve Whitehouse:
+ *   Patrick Caulfield:  Checking conninits for correctness & sending of error
+ *                       responses.
+ *    Steve Whitehouse:  Added backlog congestion level return codes.
+ *   Patrick Caulfield:
+ *    Steve Whitehouse:  Added flow control support (outbound)
+ *    Steve Whitehouse:  Prepare for nonlinear skbs
+ */
+
+/******************************************************************************
+    (c) 1995-1998 E.M. Serrat		emserrat@geocities.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
+    any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+*******************************************************************************/
+
+#include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <linux/netdevice.h>
+#include <linux/inet.h>
+#include <linux/route.h>
+#include <net/sock.h>
+#include <net/tcp.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/termios.h>      
+#include <linux/interrupt.h>
+#include <linux/proc_fs.h>
+#include <linux/stat.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/netfilter_decnet.h>
+#include <net/neighbour.h>
+#include <net/dst.h>
+#include <net/dn.h>
+#include <net/dn_nsp.h>
+#include <net/dn_dev.h>
+#include <net/dn_route.h>
+
+extern int decnet_log_martians;
+
+static void dn_log_martian(struct sk_buff *skb, const char *msg)
+{
+	if (decnet_log_martians && net_ratelimit()) {
+		char *devname = skb->dev ? skb->dev->name : "???";
+		struct dn_skb_cb *cb = DN_SKB_CB(skb);
+		printk(KERN_INFO "DECnet: Martian packet (%s) dev=%s src=0x%04hx dst=0x%04hx srcport=0x%04hx dstport=0x%04hx\n", msg, devname, cb->src, cb->dst, cb->src_port, cb->dst_port);
+	}
+}
+
+/*
+ * For this function we've flipped the cross-subchannel bit
+ * if the message is an otherdata or linkservice message. Thus
+ * we can use it to work out what to update.
+ */
+static void dn_ack(struct sock *sk, struct sk_buff *skb, unsigned short ack)
+{
+	struct dn_scp *scp = DN_SK(sk);
+	unsigned short type = ((ack >> 12) & 0x0003);
+	int wakeup = 0;
+
+	switch(type) {
+		case 0: /* ACK - Data */
+			if (dn_after(ack, scp->ackrcv_dat)) {
+				scp->ackrcv_dat = ack & 0x0fff;
+				wakeup |= dn_nsp_check_xmit_queue(sk, skb, &scp->data_xmit_queue, ack);
+			}
+			break;
+		case 1: /* NAK - Data */
+			break;
+		case 2: /* ACK - OtherData */
+			if (dn_after(ack, scp->ackrcv_oth)) {
+				scp->ackrcv_oth = ack & 0x0fff;
+				wakeup |= dn_nsp_check_xmit_queue(sk, skb, &scp->other_xmit_queue, ack);
+			}
+			break;
+		case 3: /* NAK - OtherData */
+			break;
+	}
+
+	if (wakeup && !sock_flag(sk, SOCK_DEAD))
+		sk->sk_state_change(sk);
+}
+
+/*
+ * This function is a universal ack processor.
+ */
+static int dn_process_ack(struct sock *sk, struct sk_buff *skb, int oth)
+{
+	unsigned short *ptr = (unsigned short *)skb->data;
+	int len = 0;
+	unsigned short ack;
+
+	if (skb->len < 2)
+		return len;
+
+	if ((ack = dn_ntohs(*ptr)) & 0x8000) {
+		skb_pull(skb, 2);
+		ptr++;
+		len += 2;
+		if ((ack & 0x4000) == 0) {
+			if (oth) 
+				ack ^= 0x2000;
+			dn_ack(sk, skb, ack);
+		}
+	}
+
+	if (skb->len < 2)
+		return len;
+
+	if ((ack = dn_ntohs(*ptr)) & 0x8000) {
+		skb_pull(skb, 2);
+		len += 2;
+		if ((ack & 0x4000) == 0) {
+			if (oth) 
+				ack ^= 0x2000;
+			dn_ack(sk, skb, ack);
+		}
+	}
+
+	return len;
+}
+
+
+/**
+ * dn_check_idf - Check an image data field format is correct.
+ * @pptr: Pointer to pointer to image data
+ * @len: Pointer to length of image data
+ * @max: The maximum allowed length of the data in the image data field
+ * @follow_on: Check that this many bytes exist beyond the end of the image data
+ *
+ * Returns: 0 if ok, -1 on error
+ */
+static inline int dn_check_idf(unsigned char **pptr, int *len, unsigned char max, unsigned char follow_on)
+{
+	unsigned char *ptr = *pptr;
+	unsigned char flen = *ptr++;
+
+	(*len)--;
+	if (flen > max)
+		return -1;
+	if ((flen + follow_on) > *len)
+		return -1;
+
+	*len -= flen;
+	*pptr = ptr + flen;
+	return 0;
+}
+
+/*
+ * Table of reason codes to pass back to node which sent us a badly
+ * formed message, plus text messages for the log. A zero entry in
+ * the reason field means "don't reply" otherwise a disc init is sent with
+ * the specified reason code.
+ */
+static struct {
+	unsigned short reason;
+	const char *text;
+} ci_err_table[] = {
+ { 0,             "CI: Truncated message" },
+ { NSP_REASON_ID, "CI: Destination username error" },
+ { NSP_REASON_ID, "CI: Destination username type" },
+ { NSP_REASON_US, "CI: Source username error" },
+ { 0,             "CI: Truncated at menuver" },
+ { 0,             "CI: Truncated before access or user data" },
+ { NSP_REASON_IO, "CI: Access data format error" },
+ { NSP_REASON_IO, "CI: User data format error" }
+};
+
+/*
+ * This function uses a slightly different lookup method
+ * to find its sockets, since it searches on object name/number
+ * rather than port numbers. Various tests are done to ensure that
+ * the incoming data is in the correct format before it is queued to
+ * a socket.
+ */
+static struct sock *dn_find_listener(struct sk_buff *skb, unsigned short *reason)
+{
+	struct dn_skb_cb *cb = DN_SKB_CB(skb);
+	struct nsp_conn_init_msg *msg = (struct nsp_conn_init_msg *)skb->data;
+	struct sockaddr_dn dstaddr;
+	struct sockaddr_dn srcaddr;
+	unsigned char type = 0;
+	int dstlen;
+	int srclen;
+	unsigned char *ptr;
+	int len;
+	int err = 0;
+	unsigned char menuver;
+
+	memset(&dstaddr, 0, sizeof(struct sockaddr_dn));
+	memset(&srcaddr, 0, sizeof(struct sockaddr_dn));
+
+	/*
+	 * 1. Decode & remove message header
+	 */
+	cb->src_port = msg->srcaddr;
+	cb->dst_port = msg->dstaddr;
+	cb->services = msg->services;
+	cb->info     = msg->info;
+	cb->segsize  = dn_ntohs(msg->segsize);
+
+	if (!pskb_may_pull(skb, sizeof(*msg)))
+		goto err_out;
+
+	skb_pull(skb, sizeof(*msg));
+
+	len = skb->len;
+	ptr = skb->data;
+
+	/*
+	 * 2. Check destination end username format
+	 */
+	dstlen = dn_username2sockaddr(ptr, len, &dstaddr, &type);
+	err++;
+	if (dstlen < 0)
+		goto err_out;
+
+	err++;
+	if (type > 1)
+		goto err_out;
+
+	len -= dstlen;
+	ptr += dstlen;
+
+	/*
+	 * 3. Check source end username format
+	 */
+	srclen = dn_username2sockaddr(ptr, len, &srcaddr, &type);
+	err++;
+	if (srclen < 0)
+		goto err_out;
+
+	len -= srclen;
+	ptr += srclen;
+	err++;
+	if (len < 1)
+		goto err_out;
+
+	menuver = *ptr;
+	ptr++;
+	len--;
+
+	/*
+	 * 4. Check that optional data actually exists if menuver says it does
+	 */
+	err++;
+	if ((menuver & (DN_MENUVER_ACC | DN_MENUVER_USR)) && (len < 1))
+		goto err_out;
+
+	/*
+	 * 5. Check optional access data format
+	 */
+	err++;
+	if (menuver & DN_MENUVER_ACC) {
+		if (dn_check_idf(&ptr, &len, 39, 1))
+			goto err_out;
+		if (dn_check_idf(&ptr, &len, 39, 1))
+			goto err_out;
+		if (dn_check_idf(&ptr, &len, 39, (menuver & DN_MENUVER_USR) ? 1 : 0))
+			goto err_out;
+	}
+
+	/*
+	 * 6. Check optional user data format
+	 */
+	err++;
+	if (menuver & DN_MENUVER_USR) {
+		if (dn_check_idf(&ptr, &len, 16, 0))
+			goto err_out;
+	}
+
+	/*
+	 * 7. Look up socket based on destination end username
+	 */
+	return dn_sklist_find_listener(&dstaddr);
+err_out:
+	dn_log_martian(skb, ci_err_table[err].text);
+	*reason = ci_err_table[err].reason;
+	return NULL;
+}
+
+
+static void dn_nsp_conn_init(struct sock *sk, struct sk_buff *skb)
+{
+	if (sk_acceptq_is_full(sk)) {
+		kfree_skb(skb);
+		return;
+	}
+
+	sk->sk_ack_backlog++;
+	skb_queue_tail(&sk->sk_receive_queue, skb);
+	sk->sk_state_change(sk);
+}
+
+static void dn_nsp_conn_conf(struct sock *sk, struct sk_buff *skb)
+{
+	struct dn_skb_cb *cb = DN_SKB_CB(skb);
+	struct dn_scp *scp = DN_SK(sk);
+	unsigned char *ptr;
+
+	if (skb->len < 4)
+		goto out;
+
+	ptr = skb->data;
+	cb->services = *ptr++;
+	cb->info = *ptr++;
+	cb->segsize = dn_ntohs(*(__u16 *)ptr);
+
+	if ((scp->state == DN_CI) || (scp->state == DN_CD)) {
+		scp->persist = 0;
+                scp->addrrem = cb->src_port;
+                sk->sk_state = TCP_ESTABLISHED;
+                scp->state = DN_RUN;
+		scp->services_rem = cb->services;
+		scp->info_rem = cb->info;
+		scp->segsize_rem = cb->segsize;
+
+		if ((scp->services_rem & NSP_FC_MASK) == NSP_FC_NONE)
+			scp->max_window = decnet_no_fc_max_cwnd;
+
+		if (skb->len > 0) {
+			unsigned char dlen = *skb->data;
+			if ((dlen <= 16) && (dlen <= skb->len)) {
+				scp->conndata_in.opt_optl = dlen;
+				memcpy(scp->conndata_in.opt_data, skb->data + 1, dlen);
+			}
+		}
+                dn_nsp_send_link(sk, DN_NOCHANGE, 0);
+                if (!sock_flag(sk, SOCK_DEAD))
+                	sk->sk_state_change(sk);
+        }
+
+out:
+        kfree_skb(skb);
+}
+
+static void dn_nsp_conn_ack(struct sock *sk, struct sk_buff *skb)
+{
+	struct dn_scp *scp = DN_SK(sk);
+
+	if (scp->state == DN_CI) {
+		scp->state = DN_CD;
+		scp->persist = 0;
+	}
+
+	kfree_skb(skb);
+}
+
+static void dn_nsp_disc_init(struct sock *sk, struct sk_buff *skb)
+{
+	struct dn_scp *scp = DN_SK(sk);
+	struct dn_skb_cb *cb = DN_SKB_CB(skb);
+	unsigned short reason;
+
+	if (skb->len < 2)
+		goto out;
+
+	reason = dn_ntohs(*(__u16 *)skb->data);
+	skb_pull(skb, 2);
+
+	scp->discdata_in.opt_status = reason;
+	scp->discdata_in.opt_optl   = 0;
+	memset(scp->discdata_in.opt_data, 0, 16);
+
+	if (skb->len > 0) {
+		unsigned char dlen = *skb->data;
+		if ((dlen <= 16) && (dlen <= skb->len)) {
+			scp->discdata_in.opt_optl = dlen;
+			memcpy(scp->discdata_in.opt_data, skb->data + 1, dlen);
+		}
+	}
+
+	scp->addrrem = cb->src_port;
+	sk->sk_state = TCP_CLOSE;
+
+	switch(scp->state) {
+		case DN_CI:
+		case DN_CD:
+			scp->state = DN_RJ;
+			sk->sk_err = ECONNREFUSED;
+			break;
+		case DN_RUN:
+			sk->sk_shutdown |= SHUTDOWN_MASK;
+			scp->state = DN_DN;
+			break;
+		case DN_DI:
+			scp->state = DN_DIC;
+			break;
+	}
+
+	if (!sock_flag(sk, SOCK_DEAD)) {
+		if (sk->sk_socket->state != SS_UNCONNECTED)
+			sk->sk_socket->state = SS_DISCONNECTING;
+		sk->sk_state_change(sk);
+	}
+
+	/* 
+	 * It appears that its possible for remote machines to send disc
+	 * init messages with no port identifier if we are in the CI and
+	 * possibly also the CD state. Obviously we shouldn't reply with
+	 * a message if we don't know what the end point is.
+	 */
+	if (scp->addrrem) {
+		dn_nsp_send_disc(sk, NSP_DISCCONF, NSP_REASON_DC, GFP_ATOMIC);
+	}
+	scp->persist_fxn = dn_destroy_timer;
+	scp->persist = dn_nsp_persist(sk);
+
+out:
+	kfree_skb(skb);
+}
+
+/*
+ * disc_conf messages are also called no_resources or no_link
+ * messages depending upon the "reason" field.
+ */
+static void dn_nsp_disc_conf(struct sock *sk, struct sk_buff *skb)
+{
+	struct dn_scp *scp = DN_SK(sk);
+	unsigned short reason;
+
+	if (skb->len != 2)
+		goto out;
+
+	reason = dn_ntohs(*(__u16 *)skb->data);
+
+	sk->sk_state = TCP_CLOSE;
+
+	switch(scp->state) {
+		case DN_CI:
+			scp->state = DN_NR;
+			break;
+		case DN_DR:
+			if (reason == NSP_REASON_DC)
+				scp->state = DN_DRC;
+			if (reason == NSP_REASON_NL)
+				scp->state = DN_CN;
+			break;
+		case DN_DI:
+			scp->state = DN_DIC;
+			break;
+		case DN_RUN:
+			sk->sk_shutdown |= SHUTDOWN_MASK;
+		case DN_CC:
+			scp->state = DN_CN;
+	}
+
+	if (!sock_flag(sk, SOCK_DEAD)) {
+		if (sk->sk_socket->state != SS_UNCONNECTED)
+			sk->sk_socket->state = SS_DISCONNECTING;
+		sk->sk_state_change(sk);
+	}
+
+	scp->persist_fxn = dn_destroy_timer;
+	scp->persist = dn_nsp_persist(sk);
+
+out:
+	kfree_skb(skb);
+}
+
+static void dn_nsp_linkservice(struct sock *sk, struct sk_buff *skb)
+{
+	struct dn_scp *scp = DN_SK(sk);
+	unsigned short segnum;
+	unsigned char lsflags;
+	signed char fcval;
+	int wake_up = 0;
+	char *ptr = skb->data;
+	unsigned char fctype = scp->services_rem & NSP_FC_MASK;
+
+	if (skb->len != 4)
+		goto out;
+
+	segnum = dn_ntohs(*(__u16 *)ptr);
+	ptr += 2;
+	lsflags = *(unsigned char *)ptr++;
+	fcval = *ptr;
+
+	/*
+	 * Here we ignore erronous packets which should really
+	 * should cause a connection abort. It is not critical 
+	 * for now though.
+	 */
+	if (lsflags & 0xf8)
+		goto out;
+
+	if (seq_next(scp->numoth_rcv, segnum)) {
+		seq_add(&scp->numoth_rcv, 1);
+		switch(lsflags & 0x04) { /* FCVAL INT */
+		case 0x00: /* Normal Request */
+			switch(lsflags & 0x03) { /* FCVAL MOD */
+       	         	case 0x00: /* Request count */
+				if (fcval < 0) {
+					unsigned char p_fcval = -fcval;
+					if ((scp->flowrem_dat > p_fcval) &&
+					    (fctype == NSP_FC_SCMC)) {
+						scp->flowrem_dat -= p_fcval;
+					}
+				} else if (fcval > 0) {
+					scp->flowrem_dat += fcval;
+					wake_up = 1;
+				}
+               	       	 	break;
+			case 0x01: /* Stop outgoing data */
+				scp->flowrem_sw = DN_DONTSEND;
+				break;
+			case 0x02: /* Ok to start again */
+				scp->flowrem_sw = DN_SEND;
+				dn_nsp_output(sk);
+				wake_up = 1;
+			}
+			break;
+		case 0x04: /* Interrupt Request */
+			if (fcval > 0) {
+				scp->flowrem_oth += fcval;
+				wake_up = 1;
+			}
+			break;
+                }
+		if (wake_up && !sock_flag(sk, SOCK_DEAD))
+			sk->sk_state_change(sk);
+        }
+
+	dn_nsp_send_oth_ack(sk);
+
+out:
+	kfree_skb(skb);
+}
+
+/*
+ * Copy of sock_queue_rcv_skb (from sock.h) without
+ * bh_lock_sock() (its already held when this is called) which
+ * also allows data and other data to be queued to a socket.
+ */
+static __inline__ int dn_queue_skb(struct sock *sk, struct sk_buff *skb, int sig, struct sk_buff_head *queue)
+{
+	int err;
+	
+        /* Cast skb->rcvbuf to unsigned... It's pointless, but reduces
+           number of warnings when compiling with -W --ANK
+         */
+        if (atomic_read(&sk->sk_rmem_alloc) + skb->truesize >=
+	    (unsigned)sk->sk_rcvbuf) {
+        	err = -ENOMEM;
+        	goto out;
+        }
+
+	err = sk_filter(sk, skb, 0);
+	if (err)
+		goto out;
+
+        skb_set_owner_r(skb, sk);
+        skb_queue_tail(queue, skb);
+
+	/* This code only runs from BH or BH protected context.
+	 * Therefore the plain read_lock is ok here. -DaveM
+	 */
+	read_lock(&sk->sk_callback_lock);
+        if (!sock_flag(sk, SOCK_DEAD)) {
+		struct socket *sock = sk->sk_socket;
+		wake_up_interruptible(sk->sk_sleep);
+		if (sock && sock->fasync_list &&
+		    !test_bit(SOCK_ASYNC_WAITDATA, &sock->flags))
+			__kill_fasync(sock->fasync_list, sig, 
+				    (sig == SIGURG) ? POLL_PRI : POLL_IN);
+	}
+	read_unlock(&sk->sk_callback_lock);
+out:
+        return err;
+}
+
+static void dn_nsp_otherdata(struct sock *sk, struct sk_buff *skb)
+{
+	struct dn_scp *scp = DN_SK(sk);
+	unsigned short segnum;
+	struct dn_skb_cb *cb = DN_SKB_CB(skb);
+	int queued = 0;
+
+	if (skb->len < 2)
+		goto out;
+
+	cb->segnum = segnum = dn_ntohs(*(__u16 *)skb->data);
+	skb_pull(skb, 2);
+
+	if (seq_next(scp->numoth_rcv, segnum)) {
+
+		if (dn_queue_skb(sk, skb, SIGURG, &scp->other_receive_queue) == 0) {
+			seq_add(&scp->numoth_rcv, 1);
+			scp->other_report = 0;
+			queued = 1;
+		}
+	}
+
+	dn_nsp_send_oth_ack(sk);
+out:
+	if (!queued)
+		kfree_skb(skb);
+}
+
+static void dn_nsp_data(struct sock *sk, struct sk_buff *skb)
+{
+	int queued = 0;
+	unsigned short segnum;
+	struct dn_skb_cb *cb = DN_SKB_CB(skb);
+	struct dn_scp *scp = DN_SK(sk);
+
+	if (skb->len < 2)
+		goto out;
+
+	cb->segnum = segnum = dn_ntohs(*(__u16 *)skb->data);
+	skb_pull(skb, 2);
+
+	if (seq_next(scp->numdat_rcv, segnum)) {
+                if (dn_queue_skb(sk, skb, SIGIO, &sk->sk_receive_queue) == 0) {
+			seq_add(&scp->numdat_rcv, 1);
+                	queued = 1;
+                }
+
+		if ((scp->flowloc_sw == DN_SEND) && dn_congested(sk)) {
+			scp->flowloc_sw = DN_DONTSEND;
+			dn_nsp_send_link(sk, DN_DONTSEND, 0);
+		}
+        }
+
+	dn_nsp_send_data_ack(sk);
+out:
+	if (!queued)
+		kfree_skb(skb);
+}
+
+/*
+ * If one of our conninit messages is returned, this function
+ * deals with it. It puts the socket into the NO_COMMUNICATION
+ * state.
+ */
+static void dn_returned_conn_init(struct sock *sk, struct sk_buff *skb)
+{
+	struct dn_scp *scp = DN_SK(sk);
+
+	if (scp->state == DN_CI) {
+		scp->state = DN_NC;
+		sk->sk_state = TCP_CLOSE;
+		if (!sock_flag(sk, SOCK_DEAD))
+			sk->sk_state_change(sk);
+	}
+
+	kfree_skb(skb);
+}
+
+static int dn_nsp_no_socket(struct sk_buff *skb, unsigned short reason)
+{
+	struct dn_skb_cb *cb = DN_SKB_CB(skb);
+	int ret = NET_RX_DROP;
+
+	/* Must not reply to returned packets */
+	if (cb->rt_flags & DN_RT_F_RTS)
+		goto out;
+
+	if ((reason != NSP_REASON_OK) && ((cb->nsp_flags & 0x0c) == 0x08)) {
+		switch(cb->nsp_flags & 0x70) {
+			case 0x10:
+			case 0x60: /* (Retransmitted) Connect Init */
+				dn_nsp_return_disc(skb, NSP_DISCINIT, reason);
+				ret = NET_RX_SUCCESS;
+				break;
+			case 0x20: /* Connect Confirm */
+				dn_nsp_return_disc(skb, NSP_DISCCONF, reason);
+				ret = NET_RX_SUCCESS;
+				break;
+		}
+	}
+
+out:
+	kfree_skb(skb);
+	return ret;
+}
+
+static int dn_nsp_rx_packet(struct sk_buff *skb)
+{
+	struct dn_skb_cb *cb = DN_SKB_CB(skb);
+	struct sock *sk = NULL;
+	unsigned char *ptr = (unsigned char *)skb->data;
+	unsigned short reason = NSP_REASON_NL;
+
+	if (!pskb_may_pull(skb, 2))
+		goto free_out;
+
+	skb->h.raw    = skb->data;
+	cb->nsp_flags = *ptr++;
+
+	if (decnet_debug_level & 2)
+		printk(KERN_DEBUG "dn_nsp_rx: Message type 0x%02x\n", (int)cb->nsp_flags);
+
+	if (cb->nsp_flags & 0x83) 
+		goto free_out;
+
+	/*
+	 * Filter out conninits and useless packet types
+	 */
+	if ((cb->nsp_flags & 0x0c) == 0x08) {
+		switch(cb->nsp_flags & 0x70) {
+			case 0x00: /* NOP */
+			case 0x70: /* Reserved */
+			case 0x50: /* Reserved, Phase II node init */
+				goto free_out;
+			case 0x10:
+			case 0x60:
+				if (unlikely(cb->rt_flags & DN_RT_F_RTS))
+					goto free_out;
+				sk = dn_find_listener(skb, &reason);
+				goto got_it;
+		}
+	}
+
+	if (!pskb_may_pull(skb, 3))
+		goto free_out;
+
+	/*
+	 * Grab the destination address.
+	 */
+	cb->dst_port = *(unsigned short *)ptr;
+	cb->src_port = 0;
+	ptr += 2;
+
+	/*
+	 * If not a connack, grab the source address too.
+	 */
+	if (pskb_may_pull(skb, 5)) {
+		cb->src_port = *(unsigned short *)ptr;
+		ptr += 2;
+		skb_pull(skb, 5);
+	}
+
+	/*
+	 * Returned packets...
+	 * Swap src & dst and look up in the normal way.
+	 */
+	if (unlikely(cb->rt_flags & DN_RT_F_RTS)) {
+		unsigned short tmp = cb->dst_port;
+		cb->dst_port = cb->src_port;
+		cb->src_port = tmp;
+		tmp = cb->dst;
+		cb->dst = cb->src;
+		cb->src = tmp;
+	}
+
+	/*
+	 * Find the socket to which this skb is destined.
+	 */
+	sk = dn_find_by_skb(skb);
+got_it:
+	if (sk != NULL) {
+		struct dn_scp *scp = DN_SK(sk);
+		int ret;
+
+		/* Reset backoff */
+		scp->nsp_rxtshift = 0;
+
+		/*
+		 * We linearize everything except data segments here.
+		 */
+		if (cb->nsp_flags & ~0x60) {
+			if (unlikely(skb_is_nonlinear(skb)) &&
+			    skb_linearize(skb, GFP_ATOMIC) != 0)
+				goto free_out;
+		}
+
+		bh_lock_sock(sk);
+		ret = NET_RX_SUCCESS;
+		if (decnet_debug_level & 8)
+			printk(KERN_DEBUG "NSP: 0x%02x 0x%02x 0x%04x 0x%04x %d\n",
+				(int)cb->rt_flags, (int)cb->nsp_flags, 
+				(int)cb->src_port, (int)cb->dst_port, 
+				!!sock_owned_by_user(sk));
+		if (!sock_owned_by_user(sk))
+			ret = dn_nsp_backlog_rcv(sk, skb);
+		else
+			sk_add_backlog(sk, skb);
+		bh_unlock_sock(sk);
+		sock_put(sk);
+
+		return ret;
+	}
+
+	return dn_nsp_no_socket(skb, reason);
+
+free_out:
+	kfree_skb(skb);
+	return NET_RX_DROP;
+}
+
+int dn_nsp_rx(struct sk_buff *skb)
+{
+	return NF_HOOK(PF_DECnet, NF_DN_LOCAL_IN, skb, skb->dev, NULL, dn_nsp_rx_packet);
+}
+
+/*
+ * This is the main receive routine for sockets. It is called
+ * from the above when the socket is not busy, and also from
+ * sock_release() when there is a backlog queued up.
+ */
+int dn_nsp_backlog_rcv(struct sock *sk, struct sk_buff *skb)
+{
+	struct dn_scp *scp = DN_SK(sk);
+	struct dn_skb_cb *cb = DN_SKB_CB(skb);
+
+	if (cb->rt_flags & DN_RT_F_RTS) {
+		if (cb->nsp_flags == 0x18 || cb->nsp_flags == 0x68)
+			dn_returned_conn_init(sk, skb);
+		else
+			kfree_skb(skb);
+		return NET_RX_SUCCESS;
+	}
+
+	/*
+	 * Control packet.
+	 */
+	if ((cb->nsp_flags & 0x0c) == 0x08) {
+		switch(cb->nsp_flags & 0x70) {
+			case 0x10:
+			case 0x60:
+				dn_nsp_conn_init(sk, skb);
+				break;
+			case 0x20:
+				dn_nsp_conn_conf(sk, skb);
+				break;
+			case 0x30:
+				dn_nsp_disc_init(sk, skb);
+				break;
+			case 0x40:      
+				dn_nsp_disc_conf(sk, skb);
+				break;
+		}
+
+	} else if (cb->nsp_flags == 0x24) {
+		/*
+		 * Special for connacks, 'cos they don't have
+		 * ack data or ack otherdata info.
+		 */
+		dn_nsp_conn_ack(sk, skb);
+	} else {
+		int other = 1;
+
+		/* both data and ack frames can kick a CC socket into RUN */
+		if ((scp->state == DN_CC) && !sock_flag(sk, SOCK_DEAD)) {
+			scp->state = DN_RUN;
+			sk->sk_state = TCP_ESTABLISHED;
+			sk->sk_state_change(sk);
+		}
+
+		if ((cb->nsp_flags & 0x1c) == 0)
+			other = 0;
+		if (cb->nsp_flags == 0x04)
+			other = 0;
+
+		/*
+		 * Read out ack data here, this applies equally
+		 * to data, other data, link serivce and both
+		 * ack data and ack otherdata.
+		 */
+		dn_process_ack(sk, skb, other);
+
+		/*
+		 * If we've some sort of data here then call a
+		 * suitable routine for dealing with it, otherwise
+		 * the packet is an ack and can be discarded.
+		 */
+		if ((cb->nsp_flags & 0x0c) == 0) {
+
+			if (scp->state != DN_RUN)
+				goto free_out;
+
+			switch(cb->nsp_flags) {
+				case 0x10: /* LS */
+					dn_nsp_linkservice(sk, skb);
+					break;
+				case 0x30: /* OD */
+					dn_nsp_otherdata(sk, skb);
+					break;
+				default:
+					dn_nsp_data(sk, skb);
+			}
+
+		} else { /* Ack, chuck it out here */
+free_out:
+			kfree_skb(skb);
+		}
+	}
+
+	return NET_RX_SUCCESS;
+}
+
diff --git a/net/decnet/dn_nsp_out.c b/net/decnet/dn_nsp_out.c
new file mode 100644
index 0000000..42abbf3
--- /dev/null
+++ b/net/decnet/dn_nsp_out.c
@@ -0,0 +1,782 @@
+
+/*
+ * DECnet       An implementation of the DECnet protocol suite for the LINUX
+ *              operating system.  DECnet is implemented using the  BSD Socket
+ *              interface as the means of communication with the user level.
+ *
+ *              DECnet Network Services Protocol (Output)
+ *
+ * Author:      Eduardo Marcelo Serrat <emserrat@geocities.com>
+ *
+ * Changes:
+ *
+ *    Steve Whitehouse:  Split into dn_nsp_in.c and dn_nsp_out.c from
+ *                       original dn_nsp.c.
+ *    Steve Whitehouse:  Updated to work with my new routing architecture.
+ *    Steve Whitehouse:  Added changes from Eduardo Serrat's patches.
+ *    Steve Whitehouse:  Now conninits have the "return" bit set.
+ *    Steve Whitehouse:  Fixes to check alloc'd skbs are non NULL!
+ *                       Moved output state machine into one function
+ *    Steve Whitehouse:  New output state machine
+ *         Paul Koning:  Connect Confirm message fix.
+ *      Eduardo Serrat:  Fix to stop dn_nsp_do_disc() sending malformed packets.
+ *    Steve Whitehouse:  dn_nsp_output() and friends needed a spring clean
+ *    Steve Whitehouse:  Moved dn_nsp_send() in here from route.h
+ */
+
+/******************************************************************************
+    (c) 1995-1998 E.M. Serrat		emserrat@geocities.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
+    any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+*******************************************************************************/
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <linux/netdevice.h>
+#include <linux/inet.h>
+#include <linux/route.h>
+#include <net/sock.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/termios.h>      
+#include <linux/interrupt.h>
+#include <linux/proc_fs.h>
+#include <linux/stat.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/if_packet.h>
+#include <net/neighbour.h>
+#include <net/dst.h>
+#include <net/flow.h>
+#include <net/dn.h>
+#include <net/dn_nsp.h>
+#include <net/dn_dev.h>
+#include <net/dn_route.h>
+
+
+static int nsp_backoff[NSP_MAXRXTSHIFT + 1] = { 1, 2, 4, 8, 16, 32, 64, 64, 64, 64, 64, 64, 64 };
+
+static void dn_nsp_send(struct sk_buff *skb)
+{
+	struct sock *sk = skb->sk;
+	struct dn_scp *scp = DN_SK(sk);
+	struct dst_entry *dst;
+	struct flowi fl;
+
+	skb->h.raw = skb->data;
+	scp->stamp = jiffies;
+
+	dst = sk_dst_check(sk, 0);
+	if (dst) {
+try_again:
+		skb->dst = dst;
+		dst_output(skb);
+		return;
+	}
+
+	memset(&fl, 0, sizeof(fl));
+	fl.oif = sk->sk_bound_dev_if;
+	fl.fld_src = dn_saddr2dn(&scp->addr);
+	fl.fld_dst = dn_saddr2dn(&scp->peer);
+	dn_sk_ports_copy(&fl, scp);
+	fl.proto = DNPROTO_NSP;
+	if (dn_route_output_sock(&sk->sk_dst_cache, &fl, sk, 0) == 0) {
+		dst = sk_dst_get(sk);
+		sk->sk_route_caps = dst->dev->features;
+		goto try_again;
+	}
+
+	sk->sk_err = EHOSTUNREACH;
+	if (!sock_flag(sk, SOCK_DEAD))
+		sk->sk_state_change(sk);
+}
+
+
+/*
+ * If sk == NULL, then we assume that we are supposed to be making
+ * a routing layer skb. If sk != NULL, then we are supposed to be
+ * creating an skb for the NSP layer.
+ *
+ * The eventual aim is for each socket to have a cached header size
+ * for its outgoing packets, and to set hdr from this when sk != NULL.
+ */
+struct sk_buff *dn_alloc_skb(struct sock *sk, int size, int pri)
+{
+	struct sk_buff *skb;
+	int hdr = 64;
+
+	if ((skb = alloc_skb(size + hdr, pri)) == NULL)
+		return NULL;
+
+	skb->protocol = __constant_htons(ETH_P_DNA_RT);
+	skb->pkt_type = PACKET_OUTGOING;
+
+	if (sk)
+		skb_set_owner_w(skb, sk);
+
+	skb_reserve(skb, hdr);
+
+	return skb;
+}
+
+/*
+ * Wrapper for the above, for allocs of data skbs. We try and get the
+ * whole size thats been asked for (plus 11 bytes of header). If this
+ * fails, then we try for any size over 16 bytes for SOCK_STREAMS.
+ */
+struct sk_buff *dn_alloc_send_skb(struct sock *sk, size_t *size, int noblock, long timeo, int *err)
+{
+	int space;
+	int len;
+	struct sk_buff *skb = NULL;
+
+	*err = 0;
+
+	while(skb == NULL) {
+		if (signal_pending(current)) {
+			*err = sock_intr_errno(timeo);
+			break;
+		}
+
+		if (sk->sk_shutdown & SEND_SHUTDOWN) {
+			*err = EINVAL;
+			break;
+		}
+
+		if (sk->sk_err)
+			break;
+
+		len = *size + 11;
+		space = sk->sk_sndbuf - atomic_read(&sk->sk_wmem_alloc);
+
+		if (space < len) {
+			if ((sk->sk_socket->type == SOCK_STREAM) &&
+			    (space >= (16 + 11)))
+				len = space;
+		}
+
+		if (space < len) {
+			set_bit(SOCK_ASYNC_NOSPACE, &sk->sk_socket->flags);
+			if (noblock) {
+				*err = EWOULDBLOCK;
+				break;
+			}
+
+			clear_bit(SOCK_ASYNC_WAITDATA, &sk->sk_socket->flags);
+			SOCK_SLEEP_PRE(sk)
+
+			if ((sk->sk_sndbuf - atomic_read(&sk->sk_wmem_alloc)) <
+			    len)
+				schedule();
+
+			SOCK_SLEEP_POST(sk)
+			continue;
+		}
+
+		if ((skb = dn_alloc_skb(sk, len, sk->sk_allocation)) == NULL)
+			continue;
+
+		*size = len - 11;
+	}
+
+	return skb;
+}
+
+/*
+ * Calculate persist timer based upon the smoothed round
+ * trip time and the variance. Backoff according to the
+ * nsp_backoff[] array.
+ */
+unsigned long dn_nsp_persist(struct sock *sk)
+{
+	struct dn_scp *scp = DN_SK(sk);
+
+	unsigned long t = ((scp->nsp_srtt >> 2) + scp->nsp_rttvar) >> 1;
+
+	t *= nsp_backoff[scp->nsp_rxtshift];
+
+	if (t < HZ) t = HZ;
+	if (t > (600*HZ)) t = (600*HZ);
+
+	if (scp->nsp_rxtshift < NSP_MAXRXTSHIFT)
+		scp->nsp_rxtshift++;
+
+	/* printk(KERN_DEBUG "rxtshift %lu, t=%lu\n", scp->nsp_rxtshift, t); */
+
+	return t;
+}
+
+/*
+ * This is called each time we get an estimate for the rtt
+ * on the link.
+ */
+static void dn_nsp_rtt(struct sock *sk, long rtt)
+{
+	struct dn_scp *scp = DN_SK(sk);
+	long srtt = (long)scp->nsp_srtt;
+	long rttvar = (long)scp->nsp_rttvar;
+	long delta;
+
+	/*
+	 * If the jiffies clock flips over in the middle of timestamp
+	 * gathering this value might turn out negative, so we make sure
+	 * that is it always positive here.
+	 */
+	if (rtt < 0) 
+		rtt = -rtt;
+	/*
+	 * Add new rtt to smoothed average
+	 */
+	delta = ((rtt << 3) - srtt);
+	srtt += (delta >> 3);
+	if (srtt >= 1) 
+		scp->nsp_srtt = (unsigned long)srtt;
+	else
+		scp->nsp_srtt = 1;
+
+	/*
+	 * Add new rtt varience to smoothed varience
+	 */
+	delta >>= 1;
+	rttvar += ((((delta>0)?(delta):(-delta)) - rttvar) >> 2);
+	if (rttvar >= 1) 
+		scp->nsp_rttvar = (unsigned long)rttvar;
+	else
+		scp->nsp_rttvar = 1;
+
+	/* printk(KERN_DEBUG "srtt=%lu rttvar=%lu\n", scp->nsp_srtt, scp->nsp_rttvar); */
+}
+
+/**
+ * dn_nsp_clone_and_send - Send a data packet by cloning it
+ * @skb: The packet to clone and transmit
+ * @gfp: memory allocation flag
+ *
+ * Clone a queued data or other data packet and transmit it.
+ *
+ * Returns: The number of times the packet has been sent previously
+ */
+static inline unsigned dn_nsp_clone_and_send(struct sk_buff *skb, int gfp)
+{
+	struct dn_skb_cb *cb = DN_SKB_CB(skb);
+	struct sk_buff *skb2;
+	int ret = 0;
+
+	if ((skb2 = skb_clone(skb, gfp)) != NULL) {
+		ret = cb->xmit_count;
+		cb->xmit_count++;
+		cb->stamp = jiffies;
+		skb2->sk = skb->sk;
+		dn_nsp_send(skb2);
+	}
+
+	return ret;
+}
+
+/**
+ * dn_nsp_output - Try and send something from socket queues
+ * @sk: The socket whose queues are to be investigated
+ * @gfp: The memory allocation flags
+ *
+ * Try and send the packet on the end of the data and other data queues.
+ * Other data gets priority over data, and if we retransmit a packet we
+ * reduce the window by dividing it in two.
+ *
+ */
+void dn_nsp_output(struct sock *sk)
+{
+	struct dn_scp *scp = DN_SK(sk);
+	struct sk_buff *skb;
+	unsigned reduce_win = 0;
+
+	/*
+	 * First we check for otherdata/linkservice messages
+	 */
+	if ((skb = skb_peek(&scp->other_xmit_queue)) != NULL)
+		reduce_win = dn_nsp_clone_and_send(skb, GFP_ATOMIC);
+
+	/*
+	 * If we may not send any data, we don't.
+	 * If we are still trying to get some other data down the
+	 * channel, we don't try and send any data.
+	 */
+	if (reduce_win || (scp->flowrem_sw != DN_SEND))
+		goto recalc_window;
+
+	if ((skb = skb_peek(&scp->data_xmit_queue)) != NULL)
+		reduce_win = dn_nsp_clone_and_send(skb, GFP_ATOMIC);
+
+	/*
+	 * If we've sent any frame more than once, we cut the
+	 * send window size in half. There is always a minimum
+	 * window size of one available.
+	 */
+recalc_window:
+	if (reduce_win) {
+		scp->snd_window >>= 1;
+		if (scp->snd_window < NSP_MIN_WINDOW)
+			scp->snd_window = NSP_MIN_WINDOW;
+	}
+}
+
+int dn_nsp_xmit_timeout(struct sock *sk)
+{
+	struct dn_scp *scp = DN_SK(sk);
+
+	dn_nsp_output(sk);
+
+	if (skb_queue_len(&scp->data_xmit_queue) || skb_queue_len(&scp->other_xmit_queue))
+		scp->persist = dn_nsp_persist(sk);
+
+	return 0;
+}
+
+static inline unsigned char *dn_mk_common_header(struct dn_scp *scp, struct sk_buff *skb, unsigned char msgflag, int len)
+{
+	unsigned char *ptr = skb_push(skb, len);
+
+	BUG_ON(len < 5);
+
+	*ptr++ = msgflag;
+	*((unsigned short *)ptr) = scp->addrrem;
+	ptr += 2;
+	*((unsigned short *)ptr) = scp->addrloc;
+	ptr += 2;
+	return ptr;
+}
+
+static unsigned short *dn_mk_ack_header(struct sock *sk, struct sk_buff *skb, unsigned char msgflag, int hlen, int other)
+{
+	struct dn_scp *scp = DN_SK(sk);
+	unsigned short acknum = scp->numdat_rcv & 0x0FFF;
+	unsigned short ackcrs = scp->numoth_rcv & 0x0FFF;
+	unsigned short *ptr;
+
+	BUG_ON(hlen < 9);
+
+	scp->ackxmt_dat = acknum;
+	scp->ackxmt_oth = ackcrs;
+	acknum |= 0x8000;
+	ackcrs |= 0x8000;
+
+	/* If this is an "other data/ack" message, swap acknum and ackcrs */
+	if (other) {
+		unsigned short tmp = acknum;
+		acknum = ackcrs;
+		ackcrs = tmp;
+	}
+
+	/* Set "cross subchannel" bit in ackcrs */
+	ackcrs |= 0x2000;
+
+	ptr = (unsigned short *)dn_mk_common_header(scp, skb, msgflag, hlen);
+
+	*ptr++ = dn_htons(acknum);
+	*ptr++ = dn_htons(ackcrs);
+
+	return ptr;
+}
+
+static unsigned short *dn_nsp_mk_data_header(struct sock *sk, struct sk_buff *skb, int oth)
+{
+	struct dn_scp *scp = DN_SK(sk);
+	struct dn_skb_cb *cb = DN_SKB_CB(skb);
+	unsigned short *ptr = dn_mk_ack_header(sk, skb, cb->nsp_flags, 11, oth);
+
+	if (unlikely(oth)) {
+		cb->segnum = scp->numoth;
+		seq_add(&scp->numoth, 1);
+	} else {
+		cb->segnum = scp->numdat;
+		seq_add(&scp->numdat, 1);
+	}
+	*(ptr++) = dn_htons(cb->segnum);
+
+	return ptr;
+}
+
+void dn_nsp_queue_xmit(struct sock *sk, struct sk_buff *skb, int gfp, int oth)
+{
+	struct dn_scp *scp = DN_SK(sk);
+	struct dn_skb_cb *cb = DN_SKB_CB(skb);
+	unsigned long t = ((scp->nsp_srtt >> 2) + scp->nsp_rttvar) >> 1;
+
+	cb->xmit_count = 0;
+	dn_nsp_mk_data_header(sk, skb, oth);
+
+	/*
+	 * Slow start: If we have been idle for more than
+	 * one RTT, then reset window to min size.
+	 */
+	if ((jiffies - scp->stamp) > t)
+		scp->snd_window = NSP_MIN_WINDOW;
+
+	if (oth)
+		skb_queue_tail(&scp->other_xmit_queue, skb);
+	else
+		skb_queue_tail(&scp->data_xmit_queue, skb);
+
+	if (scp->flowrem_sw != DN_SEND)
+		return;
+
+	dn_nsp_clone_and_send(skb, gfp);
+}
+
+
+int dn_nsp_check_xmit_queue(struct sock *sk, struct sk_buff *skb, struct sk_buff_head *q, unsigned short acknum)
+{
+	struct dn_skb_cb *cb = DN_SKB_CB(skb);
+	struct dn_scp *scp = DN_SK(sk);
+	struct sk_buff *skb2, *list, *ack = NULL;
+	int wakeup = 0;
+	int try_retrans = 0;
+	unsigned long reftime = cb->stamp;
+	unsigned long pkttime;
+	unsigned short xmit_count;
+	unsigned short segnum;
+
+	skb2 = q->next;
+	list = (struct sk_buff *)q;
+	while(list != skb2) {
+		struct dn_skb_cb *cb2 = DN_SKB_CB(skb2);
+
+		if (dn_before_or_equal(cb2->segnum, acknum))
+			ack = skb2;
+
+		/* printk(KERN_DEBUG "ack: %s %04x %04x\n", ack ? "ACK" : "SKIP", (int)cb2->segnum, (int)acknum); */
+
+		skb2 = skb2->next;
+
+		if (ack == NULL)
+			continue;
+
+		/* printk(KERN_DEBUG "check_xmit_queue: %04x, %d\n", acknum, cb2->xmit_count); */
+
+		/* Does _last_ packet acked have xmit_count > 1 */
+		try_retrans = 0;
+		/* Remember to wake up the sending process */
+		wakeup = 1;
+		/* Keep various statistics */
+		pkttime = cb2->stamp;
+		xmit_count = cb2->xmit_count;
+		segnum = cb2->segnum;
+		/* Remove and drop ack'ed packet */
+		skb_unlink(ack);
+		kfree_skb(ack);
+		ack = NULL;
+
+		/*
+		 * We don't expect to see acknowledgements for packets we
+		 * haven't sent yet.
+		 */
+		WARN_ON(xmit_count == 0);
+
+		/*
+		 * If the packet has only been sent once, we can use it
+		 * to calculate the RTT and also open the window a little
+		 * further.
+		 */
+		if (xmit_count == 1) {
+			if (dn_equal(segnum, acknum)) 
+				dn_nsp_rtt(sk, (long)(pkttime - reftime));
+
+			if (scp->snd_window < scp->max_window)
+				scp->snd_window++;
+		}
+
+		/*
+		 * Packet has been sent more than once. If this is the last
+		 * packet to be acknowledged then we want to send the next
+		 * packet in the send queue again (assumes the remote host does
+		 * go-back-N error control).
+		 */
+		if (xmit_count > 1)
+			try_retrans = 1;
+	}
+
+	if (try_retrans)
+		dn_nsp_output(sk);
+
+	return wakeup;
+}
+
+void dn_nsp_send_data_ack(struct sock *sk)
+{
+	struct sk_buff *skb = NULL;
+
+	if ((skb = dn_alloc_skb(sk, 9, GFP_ATOMIC)) == NULL)
+		return;
+
+	skb_reserve(skb, 9);
+	dn_mk_ack_header(sk, skb, 0x04, 9, 0);
+	dn_nsp_send(skb);
+}
+
+void dn_nsp_send_oth_ack(struct sock *sk)
+{
+	struct sk_buff *skb = NULL;
+
+	if ((skb = dn_alloc_skb(sk, 9, GFP_ATOMIC)) == NULL)
+		return;
+
+	skb_reserve(skb, 9);
+	dn_mk_ack_header(sk, skb, 0x14, 9, 1);
+	dn_nsp_send(skb);
+}
+
+
+void dn_send_conn_ack (struct sock *sk)
+{
+	struct dn_scp *scp = DN_SK(sk);
+	struct sk_buff *skb = NULL;
+        struct nsp_conn_ack_msg *msg;
+
+	if ((skb = dn_alloc_skb(sk, 3, sk->sk_allocation)) == NULL)
+		return;
+
+        msg = (struct nsp_conn_ack_msg *)skb_put(skb, 3);
+        msg->msgflg = 0x24;                   
+	msg->dstaddr = scp->addrrem;
+
+	dn_nsp_send(skb);	
+}
+
+void dn_nsp_delayed_ack(struct sock *sk)
+{
+	struct dn_scp *scp = DN_SK(sk);
+
+	if (scp->ackxmt_oth != scp->numoth_rcv)
+		dn_nsp_send_oth_ack(sk);
+
+	if (scp->ackxmt_dat != scp->numdat_rcv)
+		dn_nsp_send_data_ack(sk);
+}
+
+static int dn_nsp_retrans_conn_conf(struct sock *sk)
+{
+	struct dn_scp *scp = DN_SK(sk);
+
+	if (scp->state == DN_CC)
+		dn_send_conn_conf(sk, GFP_ATOMIC);
+
+	return 0;
+}
+
+void dn_send_conn_conf(struct sock *sk, int gfp)
+{
+	struct dn_scp *scp = DN_SK(sk);
+	struct sk_buff *skb = NULL;
+        struct nsp_conn_init_msg *msg;
+	unsigned char len = scp->conndata_out.opt_optl;
+
+	if ((skb = dn_alloc_skb(sk, 50 + scp->conndata_out.opt_optl, gfp)) == NULL)
+		return;
+
+        msg = (struct nsp_conn_init_msg *)skb_put(skb, sizeof(*msg));
+        msg->msgflg = 0x28;                   
+	msg->dstaddr = scp->addrrem;
+        msg->srcaddr = scp->addrloc;
+        msg->services = scp->services_loc;
+        msg->info = scp->info_loc;
+        msg->segsize = dn_htons(scp->segsize_loc);
+
+	*skb_put(skb,1) = len;
+
+	if (len > 0) 
+		memcpy(skb_put(skb, len), scp->conndata_out.opt_data, len);
+	
+
+	dn_nsp_send(skb);
+
+	scp->persist = dn_nsp_persist(sk);
+	scp->persist_fxn = dn_nsp_retrans_conn_conf;
+}
+
+
+static __inline__ void dn_nsp_do_disc(struct sock *sk, unsigned char msgflg, 
+			unsigned short reason, int gfp, struct dst_entry *dst,
+			int ddl, unsigned char *dd, __u16 rem, __u16 loc)
+{
+	struct sk_buff *skb = NULL;
+	int size = 7 + ddl + ((msgflg == NSP_DISCINIT) ? 1 : 0);
+	unsigned char *msg;
+
+	if ((dst == NULL) || (rem == 0)) {
+		if (net_ratelimit())
+			printk(KERN_DEBUG "DECnet: dn_nsp_do_disc: BUG! Please report this to SteveW@ACM.org rem=%u dst=%p\n", (unsigned)rem, dst);
+		return;
+	}
+
+	if ((skb = dn_alloc_skb(sk, size, gfp)) == NULL)
+		return;
+
+	msg = skb_put(skb, size);
+	*msg++ = msgflg;
+	*(__u16 *)msg = rem;
+	msg += 2;
+	*(__u16 *)msg = loc;
+	msg += 2;
+	*(__u16 *)msg = dn_htons(reason);
+	msg += 2;
+	if (msgflg == NSP_DISCINIT)
+		*msg++ = ddl;
+
+	if (ddl) {
+		memcpy(msg, dd, ddl);
+	}
+
+	/*
+	 * This doesn't go via the dn_nsp_send() function since we need
+	 * to be able to send disc packets out which have no socket
+	 * associations.
+	 */
+	skb->dst = dst_clone(dst);
+	dst_output(skb);
+}
+
+
+void dn_nsp_send_disc(struct sock *sk, unsigned char msgflg, 
+			unsigned short reason, int gfp)
+{
+	struct dn_scp *scp = DN_SK(sk);
+	int ddl = 0;
+
+	if (msgflg == NSP_DISCINIT)
+		ddl = scp->discdata_out.opt_optl;
+
+	if (reason == 0)
+		reason = scp->discdata_out.opt_status;
+
+	dn_nsp_do_disc(sk, msgflg, reason, gfp, sk->sk_dst_cache, ddl, 
+		scp->discdata_out.opt_data, scp->addrrem, scp->addrloc);
+}
+
+
+void dn_nsp_return_disc(struct sk_buff *skb, unsigned char msgflg, 
+			unsigned short reason)
+{
+	struct dn_skb_cb *cb = DN_SKB_CB(skb);
+	int ddl = 0;
+	int gfp = GFP_ATOMIC;
+
+	dn_nsp_do_disc(NULL, msgflg, reason, gfp, skb->dst, ddl, 
+			NULL, cb->src_port, cb->dst_port);
+}
+
+
+void dn_nsp_send_link(struct sock *sk, unsigned char lsflags, char fcval)
+{
+	struct dn_scp *scp = DN_SK(sk);
+	struct sk_buff *skb;
+	unsigned char *ptr;
+	int gfp = GFP_ATOMIC;
+
+	if ((skb = dn_alloc_skb(sk, DN_MAX_NSP_DATA_HEADER + 2, gfp)) == NULL)
+		return;
+
+	skb_reserve(skb, DN_MAX_NSP_DATA_HEADER);
+	ptr = skb_put(skb, 2);
+	DN_SKB_CB(skb)->nsp_flags = 0x10;
+	*ptr++ = lsflags;
+	*ptr = fcval;
+
+	dn_nsp_queue_xmit(sk, skb, gfp, 1);
+
+	scp->persist = dn_nsp_persist(sk);
+	scp->persist_fxn = dn_nsp_xmit_timeout;
+}
+
+static int dn_nsp_retrans_conninit(struct sock *sk)
+{
+	struct dn_scp *scp = DN_SK(sk);
+
+	if (scp->state == DN_CI)
+		dn_nsp_send_conninit(sk, NSP_RCI);
+
+	return 0;
+}
+
+void dn_nsp_send_conninit(struct sock *sk, unsigned char msgflg)
+{
+	struct dn_scp *scp = DN_SK(sk);
+	struct nsp_conn_init_msg *msg;
+	unsigned char aux;
+	unsigned char menuver;
+	struct dn_skb_cb *cb;
+	unsigned char type = 1;
+	int allocation = (msgflg == NSP_CI) ? sk->sk_allocation : GFP_ATOMIC;
+	struct sk_buff *skb = dn_alloc_skb(sk, 200, allocation);
+
+	if (!skb)
+		return;
+
+	cb  = DN_SKB_CB(skb);
+	msg = (struct nsp_conn_init_msg *)skb_put(skb,sizeof(*msg));
+
+	msg->msgflg	= msgflg;
+	msg->dstaddr	= 0x0000;		/* Remote Node will assign it*/
+
+	msg->srcaddr	= scp->addrloc;
+	msg->services	= scp->services_loc;	/* Requested flow control    */
+	msg->info	= scp->info_loc;	/* Version Number            */	
+	msg->segsize	= dn_htons(scp->segsize_loc);	/* Max segment size  */	
+
+	if (scp->peer.sdn_objnum)
+		type = 0;
+
+	skb_put(skb, dn_sockaddr2username(&scp->peer, skb->tail, type));
+	skb_put(skb, dn_sockaddr2username(&scp->addr, skb->tail, 2));
+
+	menuver = DN_MENUVER_ACC | DN_MENUVER_USR;
+	if (scp->peer.sdn_flags & SDF_PROXY)
+		menuver |= DN_MENUVER_PRX;
+	if (scp->peer.sdn_flags & SDF_UICPROXY)
+		menuver |= DN_MENUVER_UIC;
+
+	*skb_put(skb, 1) = menuver;	/* Menu Version		*/
+	
+	aux = scp->accessdata.acc_userl;
+	*skb_put(skb, 1) = aux;
+	if (aux > 0)
+	memcpy(skb_put(skb, aux), scp->accessdata.acc_user, aux);
+
+	aux = scp->accessdata.acc_passl;
+	*skb_put(skb, 1) = aux;
+	if (aux > 0)
+	memcpy(skb_put(skb, aux), scp->accessdata.acc_pass, aux);
+
+	aux = scp->accessdata.acc_accl;
+	*skb_put(skb, 1) = aux;
+	if (aux > 0)
+	memcpy(skb_put(skb, aux), scp->accessdata.acc_acc, aux);
+
+	aux = scp->conndata_out.opt_optl;
+	*skb_put(skb, 1) = aux;
+	if (aux > 0)
+	memcpy(skb_put(skb,aux), scp->conndata_out.opt_data, aux);
+
+	scp->persist = dn_nsp_persist(sk);
+	scp->persist_fxn = dn_nsp_retrans_conninit;
+
+	cb->rt_flags = DN_RT_F_RQR;
+
+	dn_nsp_send(skb);	
+}
+
diff --git a/net/decnet/dn_route.c b/net/decnet/dn_route.c
new file mode 100644
index 0000000..1e7b5c3
--- /dev/null
+++ b/net/decnet/dn_route.c
@@ -0,0 +1,1840 @@
+/*
+ * DECnet       An implementation of the DECnet protocol suite for the LINUX
+ *              operating system.  DECnet is implemented using the  BSD Socket
+ *              interface as the means of communication with the user level.
+ *
+ *              DECnet Routing Functions (Endnode and Router)
+ *
+ * Authors:     Steve Whitehouse <SteveW@ACM.org>
+ *              Eduardo Marcelo Serrat <emserrat@geocities.com>
+ *
+ * Changes:
+ *              Steve Whitehouse : Fixes to allow "intra-ethernet" and
+ *                                 "return-to-sender" bits on outgoing
+ *                                 packets.
+ *		Steve Whitehouse : Timeouts for cached routes.
+ *              Steve Whitehouse : Use dst cache for input routes too.
+ *              Steve Whitehouse : Fixed error values in dn_send_skb.
+ *              Steve Whitehouse : Rework routing functions to better fit
+ *                                 DECnet routing design
+ *              Alexey Kuznetsov : New SMP locking
+ *              Steve Whitehouse : More SMP locking changes & dn_cache_dump()
+ *              Steve Whitehouse : Prerouting NF hook, now really is prerouting.
+ *				   Fixed possible skb leak in rtnetlink funcs.
+ *              Steve Whitehouse : Dave Miller's dynamic hash table sizing and
+ *                                 Alexey Kuznetsov's finer grained locking
+ *                                 from ipv4/route.c.
+ *              Steve Whitehouse : Routing is now starting to look like a
+ *                                 sensible set of code now, mainly due to
+ *                                 my copying the IPv4 routing code. The
+ *                                 hooks here are modified and will continue
+ *                                 to evolve for a while.
+ *              Steve Whitehouse : Real SMP at last :-) Also new netfilter
+ *                                 stuff. Look out raw sockets your days
+ *                                 are numbered!
+ *              Steve Whitehouse : Added return-to-sender functions. Added
+ *                                 backlog congestion level return codes.
+ *		Steve Whitehouse : Fixed bug where routes were set up with
+ *                                 no ref count on net devices.
+ *              Steve Whitehouse : RCU for the route cache
+ *              Steve Whitehouse : Preparations for the flow cache
+ *              Steve Whitehouse : Prepare for nonlinear skbs
+ */
+
+/******************************************************************************
+    (c) 1995-1998 E.M. Serrat		emserrat@geocities.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
+    any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+*******************************************************************************/
+
+#include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <linux/netdevice.h>
+#include <linux/inet.h>
+#include <linux/route.h>
+#include <linux/in_route.h>
+#include <net/sock.h>
+#include <linux/mm.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/init.h>
+#include <linux/rtnetlink.h>
+#include <linux/string.h>
+#include <linux/netfilter_decnet.h>
+#include <linux/rcupdate.h>
+#include <linux/times.h>
+#include <asm/errno.h>
+#include <net/neighbour.h>
+#include <net/dst.h>
+#include <net/flow.h>
+#include <net/dn.h>
+#include <net/dn_dev.h>
+#include <net/dn_nsp.h>
+#include <net/dn_route.h>
+#include <net/dn_neigh.h>
+#include <net/dn_fib.h>
+
+struct dn_rt_hash_bucket
+{
+	struct dn_route *chain;
+	spinlock_t lock;
+} __attribute__((__aligned__(8)));
+
+extern struct neigh_table dn_neigh_table;
+
+
+static unsigned char dn_hiord_addr[6] = {0xAA,0x00,0x04,0x00,0x00,0x00};
+
+static const int dn_rt_min_delay = 2 * HZ;
+static const int dn_rt_max_delay = 10 * HZ;
+static const int dn_rt_mtu_expires = 10 * 60 * HZ;
+
+static unsigned long dn_rt_deadline;
+
+static int dn_dst_gc(void);
+static struct dst_entry *dn_dst_check(struct dst_entry *, __u32);
+static struct dst_entry *dn_dst_negative_advice(struct dst_entry *);
+static void dn_dst_link_failure(struct sk_buff *);
+static void dn_dst_update_pmtu(struct dst_entry *dst, u32 mtu);
+static int dn_route_input(struct sk_buff *);
+static void dn_run_flush(unsigned long dummy);
+
+static struct dn_rt_hash_bucket *dn_rt_hash_table;
+static unsigned dn_rt_hash_mask;
+
+static struct timer_list dn_route_timer;
+static struct timer_list dn_rt_flush_timer =
+		TIMER_INITIALIZER(dn_run_flush, 0, 0);
+int decnet_dst_gc_interval = 2;
+
+static struct dst_ops dn_dst_ops = {
+	.family =		PF_DECnet,
+	.protocol =		__constant_htons(ETH_P_DNA_RT),
+	.gc_thresh =		128,
+	.gc =			dn_dst_gc,
+	.check =		dn_dst_check,
+	.negative_advice =	dn_dst_negative_advice,
+	.link_failure =		dn_dst_link_failure,
+	.update_pmtu =		dn_dst_update_pmtu,
+	.entry_size =		sizeof(struct dn_route),
+	.entries =		ATOMIC_INIT(0),
+};
+
+static __inline__ unsigned dn_hash(unsigned short src, unsigned short dst)
+{
+	unsigned short tmp = src ^ dst;
+	tmp ^= (tmp >> 3);
+	tmp ^= (tmp >> 5);
+	tmp ^= (tmp >> 10);
+	return dn_rt_hash_mask & (unsigned)tmp;
+}
+
+static inline void dnrt_free(struct dn_route *rt)
+{
+	call_rcu_bh(&rt->u.dst.rcu_head, dst_rcu_free);
+}
+
+static inline void dnrt_drop(struct dn_route *rt)
+{
+	if (rt)
+		dst_release(&rt->u.dst);
+	call_rcu_bh(&rt->u.dst.rcu_head, dst_rcu_free);
+}
+
+static void dn_dst_check_expire(unsigned long dummy)
+{
+	int i;
+	struct dn_route *rt, **rtp;
+	unsigned long now = jiffies;
+	unsigned long expire = 120 * HZ;
+
+	for(i = 0; i <= dn_rt_hash_mask; i++) {
+		rtp = &dn_rt_hash_table[i].chain;
+
+		spin_lock(&dn_rt_hash_table[i].lock);
+		while((rt=*rtp) != NULL) {
+			if (atomic_read(&rt->u.dst.__refcnt) ||
+					(now - rt->u.dst.lastuse) < expire) {
+				rtp = &rt->u.rt_next;
+				continue;
+			}
+			*rtp = rt->u.rt_next;
+			rt->u.rt_next = NULL;
+			dnrt_free(rt);
+		}
+		spin_unlock(&dn_rt_hash_table[i].lock);
+
+		if ((jiffies - now) > 0)
+			break;
+	}
+
+	mod_timer(&dn_route_timer, now + decnet_dst_gc_interval * HZ);
+}
+
+static int dn_dst_gc(void)
+{
+	struct dn_route *rt, **rtp;
+	int i;
+	unsigned long now = jiffies;
+	unsigned long expire = 10 * HZ;
+
+	for(i = 0; i <= dn_rt_hash_mask; i++) {
+
+		spin_lock_bh(&dn_rt_hash_table[i].lock);
+		rtp = &dn_rt_hash_table[i].chain;
+
+		while((rt=*rtp) != NULL) {
+			if (atomic_read(&rt->u.dst.__refcnt) ||
+					(now - rt->u.dst.lastuse) < expire) {
+				rtp = &rt->u.rt_next;
+				continue;
+			}
+			*rtp = rt->u.rt_next;
+			rt->u.rt_next = NULL;
+			dnrt_drop(rt);
+			break;
+		}
+		spin_unlock_bh(&dn_rt_hash_table[i].lock);
+	}
+
+	return 0;
+}
+
+/*
+ * The decnet standards don't impose a particular minimum mtu, what they
+ * do insist on is that the routing layer accepts a datagram of at least
+ * 230 bytes long. Here we have to subtract the routing header length from
+ * 230 to get the minimum acceptable mtu. If there is no neighbour, then we
+ * assume the worst and use a long header size.
+ *
+ * We update both the mtu and the advertised mss (i.e. the segment size we
+ * advertise to the other end).
+ */
+static void dn_dst_update_pmtu(struct dst_entry *dst, u32 mtu)
+{
+	u32 min_mtu = 230;
+	struct dn_dev *dn = dst->neighbour ?
+			    (struct dn_dev *)dst->neighbour->dev->dn_ptr : NULL;
+
+	if (dn && dn->use_long == 0)
+		min_mtu -= 6;
+	else
+		min_mtu -= 21;
+
+	if (dst->metrics[RTAX_MTU-1] > mtu && mtu >= min_mtu) {
+		if (!(dst_metric_locked(dst, RTAX_MTU))) {
+			dst->metrics[RTAX_MTU-1] = mtu;
+			dst_set_expires(dst, dn_rt_mtu_expires);
+		}
+		if (!(dst_metric_locked(dst, RTAX_ADVMSS))) {
+			u32 mss = mtu - DN_MAX_NSP_DATA_HEADER;
+			if (dst->metrics[RTAX_ADVMSS-1] > mss)
+				dst->metrics[RTAX_ADVMSS-1] = mss;
+		}
+	}
+}
+
+/* 
+ * When a route has been marked obsolete. (e.g. routing cache flush)
+ */
+static struct dst_entry *dn_dst_check(struct dst_entry *dst, __u32 cookie)
+{
+	return NULL;
+}
+
+static struct dst_entry *dn_dst_negative_advice(struct dst_entry *dst)
+{
+	dst_release(dst);
+	return NULL;
+}
+
+static void dn_dst_link_failure(struct sk_buff *skb)
+{
+	return;
+}
+
+static inline int compare_keys(struct flowi *fl1, struct flowi *fl2)
+{
+	return memcmp(&fl1->nl_u.dn_u, &fl2->nl_u.dn_u, sizeof(fl1->nl_u.dn_u)) == 0 &&
+		fl1->oif == fl2->oif &&
+		fl1->iif == fl2->iif;
+}
+
+static int dn_insert_route(struct dn_route *rt, unsigned hash, struct dn_route **rp)
+{
+	struct dn_route *rth, **rthp;
+	unsigned long now = jiffies;
+
+	rthp = &dn_rt_hash_table[hash].chain;
+
+	spin_lock_bh(&dn_rt_hash_table[hash].lock);
+	while((rth = *rthp) != NULL) {
+		if (compare_keys(&rth->fl, &rt->fl)) {
+			/* Put it first */
+			*rthp = rth->u.rt_next;
+			rcu_assign_pointer(rth->u.rt_next,
+					   dn_rt_hash_table[hash].chain);
+			rcu_assign_pointer(dn_rt_hash_table[hash].chain, rth);
+
+			rth->u.dst.__use++;
+			dst_hold(&rth->u.dst);
+			rth->u.dst.lastuse = now;
+			spin_unlock_bh(&dn_rt_hash_table[hash].lock);
+
+			dnrt_drop(rt);
+			*rp = rth;
+			return 0;
+		}
+		rthp = &rth->u.rt_next;
+	}
+
+	rcu_assign_pointer(rt->u.rt_next, dn_rt_hash_table[hash].chain);
+	rcu_assign_pointer(dn_rt_hash_table[hash].chain, rt);
+	
+	dst_hold(&rt->u.dst);
+	rt->u.dst.__use++;
+	rt->u.dst.lastuse = now;
+	spin_unlock_bh(&dn_rt_hash_table[hash].lock);
+	*rp = rt;
+	return 0;
+}
+
+void dn_run_flush(unsigned long dummy)
+{
+	int i;
+	struct dn_route *rt, *next;
+
+	for(i = 0; i < dn_rt_hash_mask; i++) {
+		spin_lock_bh(&dn_rt_hash_table[i].lock);
+
+		if ((rt = xchg(&dn_rt_hash_table[i].chain, NULL)) == NULL)
+			goto nothing_to_declare;
+
+		for(; rt; rt=next) {
+			next = rt->u.rt_next;
+			rt->u.rt_next = NULL;
+			dst_free((struct dst_entry *)rt);
+		}
+
+nothing_to_declare:
+		spin_unlock_bh(&dn_rt_hash_table[i].lock);
+	}
+}
+
+static DEFINE_SPINLOCK(dn_rt_flush_lock);
+
+void dn_rt_cache_flush(int delay)
+{
+	unsigned long now = jiffies;
+	int user_mode = !in_interrupt();
+
+	if (delay < 0)
+		delay = dn_rt_min_delay;
+
+	spin_lock_bh(&dn_rt_flush_lock);
+
+	if (del_timer(&dn_rt_flush_timer) && delay > 0 && dn_rt_deadline) {
+		long tmo = (long)(dn_rt_deadline - now);
+
+		if (user_mode && tmo < dn_rt_max_delay - dn_rt_min_delay)
+			tmo = 0;
+
+		if (delay > tmo)
+			delay = tmo;
+	}
+
+	if (delay <= 0) {
+		spin_unlock_bh(&dn_rt_flush_lock);
+		dn_run_flush(0);
+		return;
+	}
+
+	if (dn_rt_deadline == 0)
+		dn_rt_deadline = now + dn_rt_max_delay;
+
+	dn_rt_flush_timer.expires = now + delay;
+	add_timer(&dn_rt_flush_timer);
+	spin_unlock_bh(&dn_rt_flush_lock);
+}
+
+/**
+ * dn_return_short - Return a short packet to its sender
+ * @skb: The packet to return
+ *
+ */
+static int dn_return_short(struct sk_buff *skb)
+{
+	struct dn_skb_cb *cb;
+	unsigned char *ptr;
+	dn_address *src;
+	dn_address *dst;
+	dn_address tmp;
+
+	/* Add back headers */
+	skb_push(skb, skb->data - skb->nh.raw);
+
+	if ((skb = skb_unshare(skb, GFP_ATOMIC)) == NULL)
+		return NET_RX_DROP;
+
+	cb = DN_SKB_CB(skb);
+	/* Skip packet length and point to flags */
+	ptr = skb->data + 2;
+	*ptr++ = (cb->rt_flags & ~DN_RT_F_RQR) | DN_RT_F_RTS;
+
+	dst = (dn_address *)ptr;
+	ptr += 2;
+	src = (dn_address *)ptr;
+	ptr += 2;
+	*ptr = 0; /* Zero hop count */
+
+	/* Swap source and destination */
+	tmp  = *src;
+	*src = *dst;
+	*dst = tmp;
+
+	skb->pkt_type = PACKET_OUTGOING;
+	dn_rt_finish_output(skb, NULL, NULL);
+	return NET_RX_SUCCESS;
+}
+
+/**
+ * dn_return_long - Return a long packet to its sender
+ * @skb: The long format packet to return
+ *
+ */
+static int dn_return_long(struct sk_buff *skb)
+{
+	struct dn_skb_cb *cb;
+	unsigned char *ptr;
+	unsigned char *src_addr, *dst_addr;
+	unsigned char tmp[ETH_ALEN];
+
+	/* Add back all headers */
+	skb_push(skb, skb->data - skb->nh.raw);
+
+	if ((skb = skb_unshare(skb, GFP_ATOMIC)) == NULL)
+		return NET_RX_DROP;
+
+	cb = DN_SKB_CB(skb);
+	/* Ignore packet length and point to flags */
+	ptr = skb->data + 2;
+
+	/* Skip padding */
+	if (*ptr & DN_RT_F_PF) {
+		char padlen = (*ptr & ~DN_RT_F_PF);
+		ptr += padlen;
+	}
+
+	*ptr++ = (cb->rt_flags & ~DN_RT_F_RQR) | DN_RT_F_RTS;
+	ptr += 2;
+	dst_addr = ptr;
+	ptr += 8;
+	src_addr = ptr;
+	ptr += 6;
+	*ptr = 0; /* Zero hop count */
+
+	/* Swap source and destination */
+	memcpy(tmp, src_addr, ETH_ALEN);
+	memcpy(src_addr, dst_addr, ETH_ALEN);
+	memcpy(dst_addr, tmp, ETH_ALEN);
+
+	skb->pkt_type = PACKET_OUTGOING;
+	dn_rt_finish_output(skb, dst_addr, src_addr);
+	return NET_RX_SUCCESS;
+}
+
+/**
+ * dn_route_rx_packet - Try and find a route for an incoming packet
+ * @skb: The packet to find a route for
+ *
+ * Returns: result of input function if route is found, error code otherwise
+ */
+static int dn_route_rx_packet(struct sk_buff *skb)
+{
+	struct dn_skb_cb *cb = DN_SKB_CB(skb);
+	int err;
+
+	if ((err = dn_route_input(skb)) == 0)
+		return dst_input(skb);
+
+	if (decnet_debug_level & 4) {
+		char *devname = skb->dev ? skb->dev->name : "???";
+		struct dn_skb_cb *cb = DN_SKB_CB(skb);
+		printk(KERN_DEBUG
+			"DECnet: dn_route_rx_packet: rt_flags=0x%02x dev=%s len=%d src=0x%04hx dst=0x%04hx err=%d type=%d\n",
+			(int)cb->rt_flags, devname, skb->len, cb->src, cb->dst, 
+			err, skb->pkt_type);
+	}
+
+	if ((skb->pkt_type == PACKET_HOST) && (cb->rt_flags & DN_RT_F_RQR)) {
+		switch(cb->rt_flags & DN_RT_PKT_MSK) {
+			case DN_RT_PKT_SHORT:
+				return dn_return_short(skb);
+			case DN_RT_PKT_LONG:
+				return dn_return_long(skb);
+		}
+	}
+
+	kfree_skb(skb);
+	return NET_RX_DROP;
+}
+
+static int dn_route_rx_long(struct sk_buff *skb)
+{
+	struct dn_skb_cb *cb = DN_SKB_CB(skb);
+	unsigned char *ptr = skb->data;
+
+	if (!pskb_may_pull(skb, 21)) /* 20 for long header, 1 for shortest nsp */
+		goto drop_it;
+
+	skb_pull(skb, 20);
+	skb->h.raw = skb->data;
+
+        /* Destination info */
+        ptr += 2;
+	cb->dst = dn_htons(dn_eth2dn(ptr));
+        if (memcmp(ptr, dn_hiord_addr, 4) != 0)
+                goto drop_it;
+        ptr += 6;
+
+
+        /* Source info */
+        ptr += 2;
+	cb->src = dn_htons(dn_eth2dn(ptr));
+        if (memcmp(ptr, dn_hiord_addr, 4) != 0)
+                goto drop_it;
+        ptr += 6;
+        /* Other junk */
+        ptr++;
+        cb->hops = *ptr++; /* Visit Count */
+
+	return NF_HOOK(PF_DECnet, NF_DN_PRE_ROUTING, skb, skb->dev, NULL, dn_route_rx_packet);
+
+drop_it:
+	kfree_skb(skb);
+	return NET_RX_DROP;
+}
+
+
+
+static int dn_route_rx_short(struct sk_buff *skb)
+{
+	struct dn_skb_cb *cb = DN_SKB_CB(skb);
+	unsigned char *ptr = skb->data;
+
+	if (!pskb_may_pull(skb, 6)) /* 5 for short header + 1 for shortest nsp */
+		goto drop_it;
+
+	skb_pull(skb, 5);
+	skb->h.raw = skb->data;
+
+	cb->dst = *(dn_address *)ptr;
+        ptr += 2;
+        cb->src = *(dn_address *)ptr;
+        ptr += 2;
+        cb->hops = *ptr & 0x3f;
+
+	return NF_HOOK(PF_DECnet, NF_DN_PRE_ROUTING, skb, skb->dev, NULL, dn_route_rx_packet);
+
+drop_it:
+        kfree_skb(skb);
+        return NET_RX_DROP;
+}
+
+static int dn_route_discard(struct sk_buff *skb)
+{
+	/*
+	 * I know we drop the packet here, but thats considered success in
+	 * this case
+	 */
+	kfree_skb(skb);
+	return NET_RX_SUCCESS;
+}
+
+static int dn_route_ptp_hello(struct sk_buff *skb)
+{
+	dn_dev_hello(skb);
+	dn_neigh_pointopoint_hello(skb);
+	return NET_RX_SUCCESS;
+}
+
+int dn_route_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt)
+{
+	struct dn_skb_cb *cb;
+	unsigned char flags = 0;
+	__u16 len = dn_ntohs(*(__u16 *)skb->data);
+	struct dn_dev *dn = (struct dn_dev *)dev->dn_ptr;
+	unsigned char padlen = 0;
+
+	if (dn == NULL)
+		goto dump_it;
+
+	if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL)
+		goto out;
+
+	if (!pskb_may_pull(skb, 3))
+		goto dump_it;
+
+	skb_pull(skb, 2);
+
+	if (len > skb->len)
+		goto dump_it;
+
+	skb_trim(skb, len);
+
+	flags = *skb->data;
+
+	cb = DN_SKB_CB(skb);
+	cb->stamp = jiffies;
+	cb->iif = dev->ifindex;
+
+	/*
+	 * If we have padding, remove it.
+	 */
+	if (flags & DN_RT_F_PF) {
+		padlen = flags & ~DN_RT_F_PF;
+		if (!pskb_may_pull(skb, padlen + 1))
+			goto dump_it;
+		skb_pull(skb, padlen);
+		flags = *skb->data;
+	}
+
+	skb->nh.raw = skb->data;
+
+	/*
+	 * Weed out future version DECnet
+	 */
+	if (flags & DN_RT_F_VER)
+		goto dump_it;
+
+	cb->rt_flags = flags;
+
+	if (decnet_debug_level & 1)
+		printk(KERN_DEBUG 
+			"dn_route_rcv: got 0x%02x from %s [%d %d %d]\n",
+			(int)flags, (dev) ? dev->name : "???", len, skb->len, 
+			padlen);
+
+        if (flags & DN_RT_PKT_CNTL) {
+		if (unlikely(skb_is_nonlinear(skb)) &&
+		    skb_linearize(skb, GFP_ATOMIC) != 0)
+			goto dump_it;
+
+                switch(flags & DN_RT_CNTL_MSK) {
+        	        case DN_RT_PKT_INIT:
+				dn_dev_init_pkt(skb);
+				break;
+                	case DN_RT_PKT_VERI:
+				dn_dev_veri_pkt(skb);
+				break;
+		}
+
+		if (dn->parms.state != DN_DEV_S_RU)
+			goto dump_it;
+
+		switch(flags & DN_RT_CNTL_MSK) {
+                	case DN_RT_PKT_HELO:
+				return NF_HOOK(PF_DECnet, NF_DN_HELLO, skb, skb->dev, NULL, dn_route_ptp_hello);
+
+                	case DN_RT_PKT_L1RT:
+                	case DN_RT_PKT_L2RT:
+                                return NF_HOOK(PF_DECnet, NF_DN_ROUTE, skb, skb->dev, NULL, dn_route_discard);
+                	case DN_RT_PKT_ERTH:
+				return NF_HOOK(PF_DECnet, NF_DN_HELLO, skb, skb->dev, NULL, dn_neigh_router_hello);
+
+                	case DN_RT_PKT_EEDH:
+				return NF_HOOK(PF_DECnet, NF_DN_HELLO, skb, skb->dev, NULL, dn_neigh_endnode_hello);
+                }
+        } else {
+		if (dn->parms.state != DN_DEV_S_RU)
+			goto dump_it;
+
+		skb_pull(skb, 1); /* Pull flags */
+
+                switch(flags & DN_RT_PKT_MSK) {
+                	case DN_RT_PKT_LONG:
+                        	return dn_route_rx_long(skb);
+                	case DN_RT_PKT_SHORT:
+                        	return dn_route_rx_short(skb);
+		}
+        }
+
+dump_it:
+	kfree_skb(skb);
+out:
+	return NET_RX_DROP;
+}
+
+static int dn_output(struct sk_buff *skb)
+{
+	struct dst_entry *dst = skb->dst;
+	struct dn_route *rt = (struct dn_route *)dst;
+	struct net_device *dev = dst->dev;
+	struct dn_skb_cb *cb = DN_SKB_CB(skb);
+	struct neighbour *neigh;
+
+	int err = -EINVAL;
+
+	if ((neigh = dst->neighbour) == NULL)
+		goto error;
+
+	skb->dev = dev;
+
+	cb->src = rt->rt_saddr;
+	cb->dst = rt->rt_daddr;
+
+	/*
+	 * Always set the Intra-Ethernet bit on all outgoing packets
+	 * originated on this node. Only valid flag from upper layers
+	 * is return-to-sender-requested. Set hop count to 0 too.
+	 */
+	cb->rt_flags &= ~DN_RT_F_RQR;
+	cb->rt_flags |= DN_RT_F_IE;
+	cb->hops = 0;
+
+	return NF_HOOK(PF_DECnet, NF_DN_LOCAL_OUT, skb, NULL, dev, neigh->output);
+
+error:
+	if (net_ratelimit())
+		printk(KERN_DEBUG "dn_output: This should not happen\n");
+
+	kfree_skb(skb);
+
+	return err;
+}
+
+static int dn_forward(struct sk_buff *skb)
+{
+	struct dn_skb_cb *cb = DN_SKB_CB(skb);
+	struct dst_entry *dst = skb->dst;
+	struct dn_dev *dn_db = dst->dev->dn_ptr;
+	struct dn_route *rt;
+	struct neighbour *neigh = dst->neighbour;
+	int header_len;
+#ifdef CONFIG_NETFILTER
+	struct net_device *dev = skb->dev;
+#endif
+
+	if (skb->pkt_type != PACKET_HOST)
+		goto drop;
+
+	/* Ensure that we have enough space for headers */
+	rt = (struct dn_route *)skb->dst;
+	header_len = dn_db->use_long ? 21 : 6;
+	if (skb_cow(skb, LL_RESERVED_SPACE(rt->u.dst.dev)+header_len))
+		goto drop;
+
+	/*
+	 * Hop count exceeded.
+	 */
+	if (++cb->hops > 30)
+		goto drop;
+
+	skb->dev = rt->u.dst.dev;
+
+	/*
+	 * If packet goes out same interface it came in on, then set
+	 * the Intra-Ethernet bit. This has no effect for short
+	 * packets, so we don't need to test for them here.
+	 */
+	cb->rt_flags &= ~DN_RT_F_IE;
+	if (rt->rt_flags & RTCF_DOREDIRECT)
+		cb->rt_flags |= DN_RT_F_IE;
+
+	return NF_HOOK(PF_DECnet, NF_DN_FORWARD, skb, dev, skb->dev, neigh->output);
+
+drop:
+	kfree_skb(skb);
+	return NET_RX_DROP;
+}
+
+/*
+ * Drop packet. This is used for endnodes and for
+ * when we should not be forwarding packets from
+ * this dest.
+ */
+static int dn_blackhole(struct sk_buff *skb)
+{
+	kfree_skb(skb);
+	return NET_RX_DROP;
+}
+
+/*
+ * Used to catch bugs. This should never normally get
+ * called.
+ */
+static int dn_rt_bug(struct sk_buff *skb)
+{
+	if (net_ratelimit()) {
+		struct dn_skb_cb *cb = DN_SKB_CB(skb);
+
+		printk(KERN_DEBUG "dn_rt_bug: skb from:%04x to:%04x\n",
+				cb->src, cb->dst);
+	}
+
+	kfree_skb(skb);
+
+	return NET_RX_BAD;
+}
+
+static int dn_rt_set_next_hop(struct dn_route *rt, struct dn_fib_res *res)
+{
+	struct dn_fib_info *fi = res->fi;
+	struct net_device *dev = rt->u.dst.dev;
+	struct neighbour *n;
+	unsigned mss;
+
+	if (fi) {
+		if (DN_FIB_RES_GW(*res) &&
+		    DN_FIB_RES_NH(*res).nh_scope == RT_SCOPE_LINK)
+			rt->rt_gateway = DN_FIB_RES_GW(*res);
+		memcpy(rt->u.dst.metrics, fi->fib_metrics,
+		       sizeof(rt->u.dst.metrics));
+	}
+	rt->rt_type = res->type;
+
+	if (dev != NULL && rt->u.dst.neighbour == NULL) {
+		n = __neigh_lookup_errno(&dn_neigh_table, &rt->rt_gateway, dev);
+		if (IS_ERR(n))
+			return PTR_ERR(n);
+		rt->u.dst.neighbour = n;
+	}
+
+	if (rt->u.dst.metrics[RTAX_MTU-1] == 0 || 
+            rt->u.dst.metrics[RTAX_MTU-1] > rt->u.dst.dev->mtu)
+		rt->u.dst.metrics[RTAX_MTU-1] = rt->u.dst.dev->mtu;
+	mss = dn_mss_from_pmtu(dev, dst_mtu(&rt->u.dst));
+	if (rt->u.dst.metrics[RTAX_ADVMSS-1] == 0 ||
+	    rt->u.dst.metrics[RTAX_ADVMSS-1] > mss)
+		rt->u.dst.metrics[RTAX_ADVMSS-1] = mss;
+	return 0;
+}
+
+static inline int dn_match_addr(__u16 addr1, __u16 addr2)
+{
+	__u16 tmp = dn_ntohs(addr1) ^ dn_ntohs(addr2);
+	int match = 16;
+	while(tmp) {
+		tmp >>= 1;
+		match--;
+	}
+	return match;
+}
+
+static __u16 dnet_select_source(const struct net_device *dev, __u16 daddr, int scope)
+{
+	__u16 saddr = 0;
+	struct dn_dev *dn_db = dev->dn_ptr;
+	struct dn_ifaddr *ifa;
+	int best_match = 0;
+	int ret;
+
+	read_lock(&dev_base_lock);
+	for(ifa = dn_db->ifa_list; ifa; ifa = ifa->ifa_next) {
+		if (ifa->ifa_scope > scope)
+			continue;
+		if (!daddr) {
+			saddr = ifa->ifa_local;
+			break;
+		}
+		ret = dn_match_addr(daddr, ifa->ifa_local);
+		if (ret > best_match)
+			saddr = ifa->ifa_local;
+		if (best_match == 0)
+			saddr = ifa->ifa_local;
+	}
+	read_unlock(&dev_base_lock);
+
+	return saddr;
+}
+
+static inline __u16 __dn_fib_res_prefsrc(struct dn_fib_res *res)
+{
+	return dnet_select_source(DN_FIB_RES_DEV(*res), DN_FIB_RES_GW(*res), res->scope);
+}
+
+static inline __u16 dn_fib_rules_map_destination(__u16 daddr, struct dn_fib_res *res)
+{
+	__u16 mask = dnet_make_mask(res->prefixlen);
+	return (daddr&~mask)|res->fi->fib_nh->nh_gw;
+}
+
+static int dn_route_output_slow(struct dst_entry **pprt, const struct flowi *oldflp, int try_hard)
+{
+	struct flowi fl = { .nl_u = { .dn_u = 
+				      { .daddr = oldflp->fld_dst,
+					.saddr = oldflp->fld_src,
+					.scope = RT_SCOPE_UNIVERSE,
+#ifdef CONFIG_DECNET_ROUTE_FWMARK
+					.fwmark = oldflp->fld_fwmark
+#endif
+				     } },
+			    .iif = loopback_dev.ifindex,
+			    .oif = oldflp->oif };
+	struct dn_route *rt = NULL;
+	struct net_device *dev_out = NULL;
+	struct neighbour *neigh = NULL;
+	unsigned hash;
+	unsigned flags = 0;
+	struct dn_fib_res res = { .fi = NULL, .type = RTN_UNICAST };
+	int err;
+	int free_res = 0;
+	__u16 gateway = 0;
+
+	if (decnet_debug_level & 16)
+		printk(KERN_DEBUG
+		       "dn_route_output_slow: dst=%04x src=%04x mark=%d"
+		       " iif=%d oif=%d\n", oldflp->fld_dst, oldflp->fld_src,
+                       oldflp->fld_fwmark, loopback_dev.ifindex, oldflp->oif);
+
+	/* If we have an output interface, verify its a DECnet device */
+	if (oldflp->oif) {
+		dev_out = dev_get_by_index(oldflp->oif);
+		err = -ENODEV;
+		if (dev_out && dev_out->dn_ptr == NULL) {
+			dev_put(dev_out);
+			dev_out = NULL;
+		}
+		if (dev_out == NULL)
+			goto out;
+	}
+
+	/* If we have a source address, verify that its a local address */
+	if (oldflp->fld_src) {
+		err = -EADDRNOTAVAIL;
+
+		if (dev_out) {
+			if (dn_dev_islocal(dev_out, oldflp->fld_src))
+				goto source_ok;
+			dev_put(dev_out);
+			goto out;
+		}
+		read_lock(&dev_base_lock);
+		for(dev_out = dev_base; dev_out; dev_out = dev_out->next) {
+			if (!dev_out->dn_ptr)
+				continue;
+			if (dn_dev_islocal(dev_out, oldflp->fld_src))
+				break;
+		}
+		read_unlock(&dev_base_lock);
+		if (dev_out == NULL)
+			goto out;
+		dev_hold(dev_out);
+source_ok:
+		;
+	}
+
+	/* No destination? Assume its local */
+	if (!fl.fld_dst) {
+		fl.fld_dst = fl.fld_src;
+
+		err = -EADDRNOTAVAIL;
+		if (dev_out)
+			dev_put(dev_out);
+		dev_out = &loopback_dev;
+		dev_hold(dev_out);
+		if (!fl.fld_dst) {
+			fl.fld_dst =
+			fl.fld_src = dnet_select_source(dev_out, 0,
+						       RT_SCOPE_HOST);
+			if (!fl.fld_dst)
+				goto out;
+		}
+		fl.oif = loopback_dev.ifindex;
+		res.type = RTN_LOCAL;
+		goto make_route;
+	}
+
+	if (decnet_debug_level & 16)
+		printk(KERN_DEBUG
+		       "dn_route_output_slow: initial checks complete."
+		       " dst=%o4x src=%04x oif=%d try_hard=%d\n", fl.fld_dst,
+		       fl.fld_src, fl.oif, try_hard);
+
+	/*
+	 * N.B. If the kernel is compiled without router support then
+	 * dn_fib_lookup() will evaluate to non-zero so this if () block
+	 * will always be executed.
+	 */
+	err = -ESRCH;
+	if (try_hard || (err = dn_fib_lookup(&fl, &res)) != 0) {
+		struct dn_dev *dn_db;
+		if (err != -ESRCH)
+			goto out;
+		/*
+	 	 * Here the fallback is basically the standard algorithm for 
+		 * routing in endnodes which is described in the DECnet routing
+		 * docs
+		 *
+		 * If we are not trying hard, look in neighbour cache.
+		 * The result is tested to ensure that if a specific output
+		 * device/source address was requested, then we honour that 
+		 * here
+		 */
+		if (!try_hard) {
+			neigh = neigh_lookup_nodev(&dn_neigh_table, &fl.fld_dst);
+			if (neigh) {
+				if ((oldflp->oif && 
+				    (neigh->dev->ifindex != oldflp->oif)) ||
+				    (oldflp->fld_src &&
+				    (!dn_dev_islocal(neigh->dev,
+						      oldflp->fld_src)))) {
+					neigh_release(neigh);
+					neigh = NULL;
+				} else {
+					if (dev_out)
+						dev_put(dev_out);
+					if (dn_dev_islocal(neigh->dev, fl.fld_dst)) {
+						dev_out = &loopback_dev;
+						res.type = RTN_LOCAL;
+					} else {
+						dev_out = neigh->dev;
+					}
+					dev_hold(dev_out);
+					goto select_source;
+				}
+			}
+		}
+
+		/* Not there? Perhaps its a local address */
+		if (dev_out == NULL)
+			dev_out = dn_dev_get_default();
+		err = -ENODEV;
+		if (dev_out == NULL)
+			goto out;
+		dn_db = dev_out->dn_ptr;
+		/* Possible improvement - check all devices for local addr */
+		if (dn_dev_islocal(dev_out, fl.fld_dst)) {
+			dev_put(dev_out);
+			dev_out = &loopback_dev;
+			dev_hold(dev_out);
+			res.type = RTN_LOCAL;
+			goto select_source;
+		}
+		/* Not local either.... try sending it to the default router */
+		neigh = neigh_clone(dn_db->router);
+		BUG_ON(neigh && neigh->dev != dev_out);
+
+		/* Ok then, we assume its directly connected and move on */
+select_source:
+		if (neigh)
+			gateway = ((struct dn_neigh *)neigh)->addr;
+		if (gateway == 0)
+			gateway = fl.fld_dst;
+		if (fl.fld_src == 0) {
+			fl.fld_src = dnet_select_source(dev_out, gateway,
+							 res.type == RTN_LOCAL ?
+							 RT_SCOPE_HOST : 
+							 RT_SCOPE_LINK);
+			if (fl.fld_src == 0 && res.type != RTN_LOCAL)
+				goto e_addr;
+		}
+		fl.oif = dev_out->ifindex;
+		goto make_route;
+	}
+	free_res = 1;
+
+	if (res.type == RTN_NAT)
+		goto e_inval;
+
+	if (res.type == RTN_LOCAL) {
+		if (!fl.fld_src)
+			fl.fld_src = fl.fld_dst;
+		if (dev_out)
+			dev_put(dev_out);
+		dev_out = &loopback_dev;
+		dev_hold(dev_out);
+		fl.oif = dev_out->ifindex;
+		if (res.fi)
+			dn_fib_info_put(res.fi);
+		res.fi = NULL;
+		goto make_route;
+	}
+
+	if (res.fi->fib_nhs > 1 && fl.oif == 0)
+		dn_fib_select_multipath(&fl, &res);
+
+	/* 
+	 * We could add some logic to deal with default routes here and
+	 * get rid of some of the special casing above.
+	 */
+
+	if (!fl.fld_src)
+		fl.fld_src = DN_FIB_RES_PREFSRC(res);
+	
+	if (dev_out)
+		dev_put(dev_out);
+	dev_out = DN_FIB_RES_DEV(res);
+	dev_hold(dev_out);
+	fl.oif = dev_out->ifindex;
+	gateway = DN_FIB_RES_GW(res);
+
+make_route:
+	if (dev_out->flags & IFF_LOOPBACK)
+		flags |= RTCF_LOCAL;
+
+	rt = dst_alloc(&dn_dst_ops);
+	if (rt == NULL)
+		goto e_nobufs;
+
+	atomic_set(&rt->u.dst.__refcnt, 1);
+	rt->u.dst.flags   = DST_HOST;
+
+	rt->fl.fld_src    = oldflp->fld_src;
+	rt->fl.fld_dst    = oldflp->fld_dst;
+	rt->fl.oif        = oldflp->oif;
+	rt->fl.iif        = 0;
+#ifdef CONFIG_DECNET_ROUTE_FWMARK
+	rt->fl.fld_fwmark = oldflp->fld_fwmark;
+#endif
+
+	rt->rt_saddr      = fl.fld_src;
+	rt->rt_daddr      = fl.fld_dst;
+	rt->rt_gateway    = gateway ? gateway : fl.fld_dst;
+	rt->rt_local_src  = fl.fld_src;
+
+	rt->rt_dst_map    = fl.fld_dst;
+	rt->rt_src_map    = fl.fld_src;
+
+	rt->u.dst.dev = dev_out;
+	dev_hold(dev_out);
+	rt->u.dst.neighbour = neigh;
+	neigh = NULL;
+
+	rt->u.dst.lastuse = jiffies;
+	rt->u.dst.output  = dn_output;
+	rt->u.dst.input   = dn_rt_bug;
+	rt->rt_flags      = flags;
+	if (flags & RTCF_LOCAL)
+		rt->u.dst.input = dn_nsp_rx;
+
+	err = dn_rt_set_next_hop(rt, &res);
+	if (err)
+		goto e_neighbour;
+
+	hash = dn_hash(rt->fl.fld_src, rt->fl.fld_dst);
+	dn_insert_route(rt, hash, (struct dn_route **)pprt);
+
+done:
+	if (neigh)
+		neigh_release(neigh);
+	if (free_res)
+		dn_fib_res_put(&res);
+	if (dev_out)
+		dev_put(dev_out);
+out:
+	return err;
+
+e_addr:
+        err = -EADDRNOTAVAIL;
+        goto done;
+e_inval:
+	err = -EINVAL;
+	goto done;
+e_nobufs:
+	err = -ENOBUFS;
+	goto done;
+e_neighbour:
+	dst_free(&rt->u.dst);
+	goto e_nobufs;
+}
+
+
+/*
+ * N.B. The flags may be moved into the flowi at some future stage.
+ */
+static int __dn_route_output_key(struct dst_entry **pprt, const struct flowi *flp, int flags)
+{
+	unsigned hash = dn_hash(flp->fld_src, flp->fld_dst);
+	struct dn_route *rt = NULL;
+
+	if (!(flags & MSG_TRYHARD)) {
+		rcu_read_lock_bh();
+		for(rt = rcu_dereference(dn_rt_hash_table[hash].chain); rt;
+			rt = rcu_dereference(rt->u.rt_next)) {
+			if ((flp->fld_dst == rt->fl.fld_dst) &&
+			    (flp->fld_src == rt->fl.fld_src) &&
+#ifdef CONFIG_DECNET_ROUTE_FWMARK
+			    (flp->fld_fwmark == rt->fl.fld_fwmark) &&
+#endif
+			    (rt->fl.iif == 0) &&
+			    (rt->fl.oif == flp->oif)) {
+				rt->u.dst.lastuse = jiffies;
+				dst_hold(&rt->u.dst);
+				rt->u.dst.__use++;
+				rcu_read_unlock_bh();
+				*pprt = &rt->u.dst;
+				return 0;
+			}
+		}
+		rcu_read_unlock_bh();
+	}
+
+	return dn_route_output_slow(pprt, flp, flags);
+}
+
+static int dn_route_output_key(struct dst_entry **pprt, struct flowi *flp, int flags)
+{
+	int err;
+
+	err = __dn_route_output_key(pprt, flp, flags);
+	if (err == 0 && flp->proto) {
+		err = xfrm_lookup(pprt, flp, NULL, 0);
+	}
+	return err;
+}
+
+int dn_route_output_sock(struct dst_entry **pprt, struct flowi *fl, struct sock *sk, int flags)
+{
+	int err;
+
+	err = __dn_route_output_key(pprt, fl, flags & MSG_TRYHARD);
+	if (err == 0 && fl->proto) {
+		err = xfrm_lookup(pprt, fl, sk, !(flags & MSG_DONTWAIT));
+	}
+	return err;
+}
+
+static int dn_route_input_slow(struct sk_buff *skb)
+{
+	struct dn_route *rt = NULL;
+	struct dn_skb_cb *cb = DN_SKB_CB(skb);
+	struct net_device *in_dev = skb->dev;
+	struct net_device *out_dev = NULL;
+	struct dn_dev *dn_db;
+	struct neighbour *neigh = NULL;
+	unsigned hash;
+	int flags = 0;
+	__u16 gateway = 0;
+	__u16 local_src = 0;
+	struct flowi fl = { .nl_u = { .dn_u = 
+				     { .daddr = cb->dst,
+				       .saddr = cb->src,
+				       .scope = RT_SCOPE_UNIVERSE,
+#ifdef CONFIG_DECNET_ROUTE_FWMARK
+				       .fwmark = skb->nfmark
+#endif
+				    } },
+			    .iif = skb->dev->ifindex };
+	struct dn_fib_res res = { .fi = NULL, .type = RTN_UNREACHABLE };
+	int err = -EINVAL;
+	int free_res = 0;
+
+	dev_hold(in_dev);
+
+	if ((dn_db = in_dev->dn_ptr) == NULL)
+		goto out;
+
+	/* Zero source addresses are not allowed */
+	if (fl.fld_src == 0)
+		goto out;
+
+	/*
+	 * In this case we've just received a packet from a source
+	 * outside ourselves pretending to come from us. We don't
+	 * allow it any further to prevent routing loops, spoofing and
+	 * other nasties. Loopback packets already have the dst attached
+	 * so this only affects packets which have originated elsewhere.
+	 */
+	err  = -ENOTUNIQ;
+	if (dn_dev_islocal(in_dev, cb->src))
+		goto out;
+
+	err = dn_fib_lookup(&fl, &res);
+	if (err) {
+		if (err != -ESRCH)
+			goto out;
+		/*
+		 * Is the destination us ?
+		 */
+		if (!dn_dev_islocal(in_dev, cb->dst))
+			goto e_inval;
+
+		res.type = RTN_LOCAL;
+		flags |= RTCF_DIRECTSRC;
+	} else {
+		__u16 src_map = fl.fld_src;
+		free_res = 1;
+
+		out_dev = DN_FIB_RES_DEV(res);
+		if (out_dev == NULL) {
+			if (net_ratelimit())
+				printk(KERN_CRIT "Bug in dn_route_input_slow() "
+						 "No output device\n");
+			goto e_inval;
+		}
+		dev_hold(out_dev);
+
+		if (res.r)
+			src_map = dn_fib_rules_policy(fl.fld_src, &res, &flags);
+
+		gateway = DN_FIB_RES_GW(res);
+		if (res.type == RTN_NAT) {
+			fl.fld_dst = dn_fib_rules_map_destination(fl.fld_dst, &res);
+			dn_fib_res_put(&res);
+			free_res = 0;
+			if (dn_fib_lookup(&fl, &res))
+				goto e_inval;
+			free_res = 1;
+			if (res.type != RTN_UNICAST)
+				goto e_inval;
+			flags |= RTCF_DNAT;
+			gateway = fl.fld_dst;
+		}
+		fl.fld_src = src_map;
+	}
+
+	switch(res.type) {
+	case RTN_UNICAST:
+		/*
+		 * Forwarding check here, we only check for forwarding
+		 * being turned off, if you want to only forward intra
+		 * area, its up to you to set the routing tables up
+		 * correctly.
+		 */
+		if (dn_db->parms.forwarding == 0)
+			goto e_inval;
+
+		if (res.fi->fib_nhs > 1 && fl.oif == 0)
+			dn_fib_select_multipath(&fl, &res);
+
+		/* 
+		 * Check for out_dev == in_dev. We use the RTCF_DOREDIRECT
+		 * flag as a hint to set the intra-ethernet bit when
+		 * forwarding. If we've got NAT in operation, we don't do
+		 * this optimisation.
+		 */
+		if (out_dev == in_dev && !(flags & RTCF_NAT))
+			flags |= RTCF_DOREDIRECT;
+
+		local_src = DN_FIB_RES_PREFSRC(res);
+
+	case RTN_BLACKHOLE:
+	case RTN_UNREACHABLE:
+		break;
+	case RTN_LOCAL:
+		flags |= RTCF_LOCAL;
+		fl.fld_src = cb->dst;
+		fl.fld_dst = cb->src;
+
+		/* Routing tables gave us a gateway */
+		if (gateway)
+			goto make_route;
+
+		/* Packet was intra-ethernet, so we know its on-link */
+		if (cb->rt_flags | DN_RT_F_IE) {
+			gateway = cb->src;
+			flags |= RTCF_DIRECTSRC;
+			goto make_route;
+		}
+
+		/* Use the default router if there is one */
+		neigh = neigh_clone(dn_db->router);
+		if (neigh) {
+			gateway = ((struct dn_neigh *)neigh)->addr;
+			goto make_route;
+		}
+
+		/* Close eyes and pray */
+		gateway = cb->src;
+		flags |= RTCF_DIRECTSRC;
+		goto make_route;
+	default:
+		goto e_inval;
+	}
+
+make_route:
+	rt = dst_alloc(&dn_dst_ops);
+	if (rt == NULL)
+		goto e_nobufs;
+
+	rt->rt_saddr      = fl.fld_src;
+	rt->rt_daddr      = fl.fld_dst;
+	rt->rt_gateway    = fl.fld_dst;
+	if (gateway)
+		rt->rt_gateway = gateway;
+	rt->rt_local_src  = local_src ? local_src : rt->rt_saddr;
+
+	rt->rt_dst_map    = fl.fld_dst;
+	rt->rt_src_map    = fl.fld_src;
+
+	rt->fl.fld_src    = cb->src;
+	rt->fl.fld_dst    = cb->dst;
+	rt->fl.oif        = 0;
+	rt->fl.iif        = in_dev->ifindex;
+	rt->fl.fld_fwmark = fl.fld_fwmark;
+
+	rt->u.dst.flags = DST_HOST;
+	rt->u.dst.neighbour = neigh;
+	rt->u.dst.dev = out_dev;
+	rt->u.dst.lastuse = jiffies;
+	rt->u.dst.output = dn_rt_bug;
+	switch(res.type) {
+		case RTN_UNICAST:
+			rt->u.dst.input = dn_forward;
+			break;
+		case RTN_LOCAL:
+			rt->u.dst.output = dn_output;
+			rt->u.dst.input = dn_nsp_rx;
+			rt->u.dst.dev = in_dev;
+			flags |= RTCF_LOCAL;
+			break;
+		default:
+		case RTN_UNREACHABLE:
+		case RTN_BLACKHOLE:
+			rt->u.dst.input = dn_blackhole;
+	}
+	rt->rt_flags = flags;
+	if (rt->u.dst.dev)
+		dev_hold(rt->u.dst.dev);
+
+	err = dn_rt_set_next_hop(rt, &res);
+	if (err)
+		goto e_neighbour;
+
+	hash = dn_hash(rt->fl.fld_src, rt->fl.fld_dst);
+	dn_insert_route(rt, hash, (struct dn_route **)&skb->dst);
+
+done:
+	if (neigh)
+		neigh_release(neigh);
+	if (free_res)
+		dn_fib_res_put(&res);
+	dev_put(in_dev);
+	if (out_dev)
+		dev_put(out_dev);
+out:
+	return err;
+
+e_inval:
+	err = -EINVAL;
+	goto done;
+
+e_nobufs:
+	err = -ENOBUFS;
+	goto done;
+
+e_neighbour:
+	dst_free(&rt->u.dst);
+	goto done;
+}
+
+int dn_route_input(struct sk_buff *skb)
+{
+	struct dn_route *rt;
+	struct dn_skb_cb *cb = DN_SKB_CB(skb);
+	unsigned hash = dn_hash(cb->src, cb->dst);
+
+	if (skb->dst)
+		return 0;
+
+	rcu_read_lock();
+	for(rt = rcu_dereference(dn_rt_hash_table[hash].chain); rt != NULL;
+	    rt = rcu_dereference(rt->u.rt_next)) {
+		if ((rt->fl.fld_src == cb->src) &&
+	 	    (rt->fl.fld_dst == cb->dst) &&
+		    (rt->fl.oif == 0) &&
+#ifdef CONFIG_DECNET_ROUTE_FWMARK
+		    (rt->fl.fld_fwmark == skb->nfmark) &&
+#endif
+		    (rt->fl.iif == cb->iif)) {
+			rt->u.dst.lastuse = jiffies;
+			dst_hold(&rt->u.dst);
+			rt->u.dst.__use++;
+			rcu_read_unlock();
+			skb->dst = (struct dst_entry *)rt;
+			return 0;
+		}
+	}
+	rcu_read_unlock();
+
+	return dn_route_input_slow(skb);
+}
+
+static int dn_rt_fill_info(struct sk_buff *skb, u32 pid, u32 seq, int event, int nowait)
+{
+	struct dn_route *rt = (struct dn_route *)skb->dst;
+	struct rtmsg *r;
+	struct nlmsghdr *nlh;
+	unsigned char *b = skb->tail;
+	struct rta_cacheinfo ci;
+
+	nlh = NLMSG_PUT(skb, pid, seq, event, sizeof(*r));
+	r = NLMSG_DATA(nlh);
+	nlh->nlmsg_flags = (nowait && pid) ? NLM_F_MULTI : 0;
+	r->rtm_family = AF_DECnet;
+	r->rtm_dst_len = 16;
+	r->rtm_src_len = 0;
+	r->rtm_tos = 0;
+	r->rtm_table = RT_TABLE_MAIN;
+	r->rtm_type = rt->rt_type;
+	r->rtm_flags = (rt->rt_flags & ~0xFFFF) | RTM_F_CLONED;
+	r->rtm_scope = RT_SCOPE_UNIVERSE;
+	r->rtm_protocol = RTPROT_UNSPEC;
+	if (rt->rt_flags & RTCF_NOTIFY)
+		r->rtm_flags |= RTM_F_NOTIFY;
+	RTA_PUT(skb, RTA_DST, 2, &rt->rt_daddr);
+	if (rt->fl.fld_src) {
+		r->rtm_src_len = 16;
+		RTA_PUT(skb, RTA_SRC, 2, &rt->fl.fld_src);
+	}
+	if (rt->u.dst.dev)
+		RTA_PUT(skb, RTA_OIF, sizeof(int), &rt->u.dst.dev->ifindex);
+	/*
+	 * Note to self - change this if input routes reverse direction when
+	 * they deal only with inputs and not with replies like they do
+	 * currently.
+	 */
+	RTA_PUT(skb, RTA_PREFSRC, 2, &rt->rt_local_src);
+	if (rt->rt_daddr != rt->rt_gateway)
+		RTA_PUT(skb, RTA_GATEWAY, 2, &rt->rt_gateway);
+	if (rtnetlink_put_metrics(skb, rt->u.dst.metrics) < 0)
+		goto rtattr_failure;
+	ci.rta_lastuse = jiffies_to_clock_t(jiffies - rt->u.dst.lastuse);
+	ci.rta_used     = rt->u.dst.__use;
+	ci.rta_clntref  = atomic_read(&rt->u.dst.__refcnt);
+	if (rt->u.dst.expires)
+		ci.rta_expires = jiffies_to_clock_t(rt->u.dst.expires - jiffies);
+	else
+		ci.rta_expires = 0;
+	ci.rta_error    = rt->u.dst.error;
+	ci.rta_id       = ci.rta_ts = ci.rta_tsage = 0;
+	RTA_PUT(skb, RTA_CACHEINFO, sizeof(ci), &ci);
+	if (rt->fl.iif)
+		RTA_PUT(skb, RTA_IIF, sizeof(int), &rt->fl.iif);
+
+	nlh->nlmsg_len = skb->tail - b;
+	return skb->len;
+
+nlmsg_failure:
+rtattr_failure:
+        skb_trim(skb, b - skb->data);
+        return -1;
+}
+
+/*
+ * This is called by both endnodes and routers now.
+ */
+int dn_cache_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh, void *arg)
+{
+	struct rtattr **rta = arg;
+	struct rtmsg *rtm = NLMSG_DATA(nlh);
+	struct dn_route *rt = NULL;
+	struct dn_skb_cb *cb;
+	int err;
+	struct sk_buff *skb;
+	struct flowi fl;
+
+	memset(&fl, 0, sizeof(fl));
+	fl.proto = DNPROTO_NSP;
+
+	skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
+	if (skb == NULL)
+		return -ENOBUFS;
+	skb->mac.raw = skb->data;
+	cb = DN_SKB_CB(skb);
+
+	if (rta[RTA_SRC-1])
+		memcpy(&fl.fld_src, RTA_DATA(rta[RTA_SRC-1]), 2);
+	if (rta[RTA_DST-1])
+		memcpy(&fl.fld_dst, RTA_DATA(rta[RTA_DST-1]), 2);
+	if (rta[RTA_IIF-1])
+		memcpy(&fl.iif, RTA_DATA(rta[RTA_IIF-1]), sizeof(int));
+
+	if (fl.iif) {
+		struct net_device *dev;
+		if ((dev = dev_get_by_index(fl.iif)) == NULL) {
+			kfree_skb(skb);
+			return -ENODEV;
+		}
+		if (!dev->dn_ptr) {
+			dev_put(dev);
+			kfree_skb(skb);
+			return -ENODEV;
+		}
+		skb->protocol = __constant_htons(ETH_P_DNA_RT);
+		skb->dev = dev;
+		cb->src = fl.fld_src;
+		cb->dst = fl.fld_dst;
+		local_bh_disable();
+		err = dn_route_input(skb);
+		local_bh_enable();
+		memset(cb, 0, sizeof(struct dn_skb_cb));
+		rt = (struct dn_route *)skb->dst;
+		if (!err && -rt->u.dst.error)
+			err = rt->u.dst.error;
+	} else {
+		int oif = 0;
+		if (rta[RTA_OIF - 1])
+			memcpy(&oif, RTA_DATA(rta[RTA_OIF - 1]), sizeof(int));
+		fl.oif = oif;
+		err = dn_route_output_key((struct dst_entry **)&rt, &fl, 0);
+	}
+
+	if (skb->dev)
+		dev_put(skb->dev);
+	skb->dev = NULL;
+	if (err)
+		goto out_free;
+	skb->dst = &rt->u.dst;
+	if (rtm->rtm_flags & RTM_F_NOTIFY)
+		rt->rt_flags |= RTCF_NOTIFY;
+
+	NETLINK_CB(skb).dst_pid = NETLINK_CB(in_skb).pid;
+
+	err = dn_rt_fill_info(skb, NETLINK_CB(in_skb).pid, nlh->nlmsg_seq, RTM_NEWROUTE, 0);
+
+	if (err == 0)
+		goto out_free;
+	if (err < 0) {
+		err = -EMSGSIZE;
+		goto out_free;
+	}
+
+	err = netlink_unicast(rtnl, skb, NETLINK_CB(in_skb).pid, MSG_DONTWAIT);
+
+	return err;
+
+out_free:
+	kfree_skb(skb);
+	return err;
+}
+
+/*
+ * For routers, this is called from dn_fib_dump, but for endnodes its
+ * called directly from the rtnetlink dispatch table.
+ */
+int dn_cache_dump(struct sk_buff *skb, struct netlink_callback *cb)
+{
+	struct dn_route *rt;
+	int h, s_h;
+	int idx, s_idx;
+
+	if (NLMSG_PAYLOAD(cb->nlh, 0) < sizeof(struct rtmsg))
+		return -EINVAL;
+	if (!(((struct rtmsg *)NLMSG_DATA(cb->nlh))->rtm_flags&RTM_F_CLONED))
+		return 0;
+
+	s_h = cb->args[0];
+	s_idx = idx = cb->args[1];
+	for(h = 0; h <= dn_rt_hash_mask; h++) {
+		if (h < s_h)
+			continue;
+		if (h > s_h)
+			s_idx = 0;
+		rcu_read_lock_bh();
+		for(rt = rcu_dereference(dn_rt_hash_table[h].chain), idx = 0;
+			rt;
+			rt = rcu_dereference(rt->u.rt_next), idx++) {
+			if (idx < s_idx)
+				continue;
+			skb->dst = dst_clone(&rt->u.dst);
+			if (dn_rt_fill_info(skb, NETLINK_CB(cb->skb).pid,
+					cb->nlh->nlmsg_seq, RTM_NEWROUTE, 1) <= 0) {
+				dst_release(xchg(&skb->dst, NULL));
+				rcu_read_unlock_bh();
+				goto done;
+			}
+			dst_release(xchg(&skb->dst, NULL));
+		}
+		rcu_read_unlock_bh();
+	}
+
+done:
+	cb->args[0] = h;
+	cb->args[1] = idx;
+	return skb->len;
+}
+
+#ifdef CONFIG_PROC_FS
+struct dn_rt_cache_iter_state {
+	int bucket;
+};
+
+static struct dn_route *dn_rt_cache_get_first(struct seq_file *seq)
+{
+	struct dn_route *rt = NULL;
+	struct dn_rt_cache_iter_state *s = seq->private;
+
+	for(s->bucket = dn_rt_hash_mask; s->bucket >= 0; --s->bucket) {
+		rcu_read_lock_bh();
+		rt = dn_rt_hash_table[s->bucket].chain;
+		if (rt)
+			break;
+		rcu_read_unlock_bh();
+	}
+	return rt;
+}
+
+static struct dn_route *dn_rt_cache_get_next(struct seq_file *seq, struct dn_route *rt)
+{
+	struct dn_rt_cache_iter_state *s = rcu_dereference(seq->private);
+
+	rt = rt->u.rt_next;
+	while(!rt) {
+		rcu_read_unlock_bh();
+		if (--s->bucket < 0)
+			break;
+		rcu_read_lock_bh();
+		rt = dn_rt_hash_table[s->bucket].chain;
+	}
+	return rt;
+}
+
+static void *dn_rt_cache_seq_start(struct seq_file *seq, loff_t *pos)
+{
+	struct dn_route *rt = dn_rt_cache_get_first(seq);
+
+	if (rt) {
+		while(*pos && (rt = dn_rt_cache_get_next(seq, rt)))
+			--*pos;
+	}
+	return *pos ? NULL : rt;
+}
+
+static void *dn_rt_cache_seq_next(struct seq_file *seq, void *v, loff_t *pos)
+{
+	struct dn_route *rt = dn_rt_cache_get_next(seq, v);
+	++*pos;
+	return rt;
+}
+
+static void dn_rt_cache_seq_stop(struct seq_file *seq, void *v)
+{
+	if (v)
+		rcu_read_unlock_bh();
+}
+
+static int dn_rt_cache_seq_show(struct seq_file *seq, void *v)
+{
+	struct dn_route *rt = v;
+	char buf1[DN_ASCBUF_LEN], buf2[DN_ASCBUF_LEN];
+
+	seq_printf(seq, "%-8s %-7s %-7s %04d %04d %04d\n",
+			rt->u.dst.dev ? rt->u.dst.dev->name : "*",
+			dn_addr2asc(dn_ntohs(rt->rt_daddr), buf1),
+			dn_addr2asc(dn_ntohs(rt->rt_saddr), buf2),
+			atomic_read(&rt->u.dst.__refcnt),
+			rt->u.dst.__use,
+			(int) dst_metric(&rt->u.dst, RTAX_RTT));
+	return 0;
+} 
+
+static struct seq_operations dn_rt_cache_seq_ops = {
+	.start	= dn_rt_cache_seq_start,
+	.next	= dn_rt_cache_seq_next,
+	.stop	= dn_rt_cache_seq_stop,
+	.show	= dn_rt_cache_seq_show,
+};
+
+static int dn_rt_cache_seq_open(struct inode *inode, struct file *file)
+{
+	struct seq_file *seq;
+	int rc = -ENOMEM;
+	struct dn_rt_cache_iter_state *s = kmalloc(sizeof(*s), GFP_KERNEL);
+
+	if (!s)
+		goto out;
+	rc = seq_open(file, &dn_rt_cache_seq_ops);
+	if (rc)
+		goto out_kfree;
+	seq		= file->private_data;
+	seq->private	= s;
+	memset(s, 0, sizeof(*s));
+out:
+	return rc;
+out_kfree:
+	kfree(s);
+	goto out;
+}
+
+static struct file_operations dn_rt_cache_seq_fops = {
+	.owner	 = THIS_MODULE,
+	.open	 = dn_rt_cache_seq_open,
+	.read	 = seq_read,
+	.llseek	 = seq_lseek,
+	.release = seq_release_private,
+};
+
+#endif /* CONFIG_PROC_FS */
+
+void __init dn_route_init(void)
+{
+	int i, goal, order;
+
+	dn_dst_ops.kmem_cachep = kmem_cache_create("dn_dst_cache",
+						   sizeof(struct dn_route),
+						   0, SLAB_HWCACHE_ALIGN,
+						   NULL, NULL);
+
+	if (!dn_dst_ops.kmem_cachep)
+		panic("DECnet: Failed to allocate dn_dst_cache\n");
+
+	init_timer(&dn_route_timer);
+	dn_route_timer.function = dn_dst_check_expire;
+	dn_route_timer.expires = jiffies + decnet_dst_gc_interval * HZ;
+	add_timer(&dn_route_timer);
+
+	goal = num_physpages >> (26 - PAGE_SHIFT);
+
+	for(order = 0; (1UL << order) < goal; order++)
+		/* NOTHING */;
+
+        /*
+         * Only want 1024 entries max, since the table is very, very unlikely
+         * to be larger than that.
+         */
+        while(order && ((((1UL << order) * PAGE_SIZE) / 
+                                sizeof(struct dn_rt_hash_bucket)) >= 2048))
+                order--;
+
+        do {
+                dn_rt_hash_mask = (1UL << order) * PAGE_SIZE /
+                        sizeof(struct dn_rt_hash_bucket);
+                while(dn_rt_hash_mask & (dn_rt_hash_mask - 1))
+                        dn_rt_hash_mask--;
+                dn_rt_hash_table = (struct dn_rt_hash_bucket *)
+                        __get_free_pages(GFP_ATOMIC, order);
+        } while (dn_rt_hash_table == NULL && --order > 0);
+
+	if (!dn_rt_hash_table)
+                panic("Failed to allocate DECnet route cache hash table\n");
+
+	printk(KERN_INFO 
+		"DECnet: Routing cache hash table of %u buckets, %ldKbytes\n", 
+		dn_rt_hash_mask, 
+		(long)(dn_rt_hash_mask*sizeof(struct dn_rt_hash_bucket))/1024);
+
+	dn_rt_hash_mask--;
+        for(i = 0; i <= dn_rt_hash_mask; i++) {
+                spin_lock_init(&dn_rt_hash_table[i].lock);
+                dn_rt_hash_table[i].chain = NULL;
+        }
+
+        dn_dst_ops.gc_thresh = (dn_rt_hash_mask + 1);
+
+	proc_net_fops_create("decnet_cache", S_IRUGO, &dn_rt_cache_seq_fops);
+}
+
+void __exit dn_route_cleanup(void)
+{
+	del_timer(&dn_route_timer);
+	dn_run_flush(0);
+
+	proc_net_remove("decnet_cache");
+}
+
diff --git a/net/decnet/dn_rules.c b/net/decnet/dn_rules.c
new file mode 100644
index 0000000..597587d
--- /dev/null
+++ b/net/decnet/dn_rules.c
@@ -0,0 +1,416 @@
+
+/*
+ * DECnet       An implementation of the DECnet protocol suite for the LINUX
+ *              operating system.  DECnet is implemented using the  BSD Socket
+ *              interface as the means of communication with the user level.
+ *
+ *              DECnet Routing Forwarding Information Base (Rules)
+ *
+ * Author:      Steve Whitehouse <SteveW@ACM.org>
+ *              Mostly copied from Alexey Kuznetsov's ipv4/fib_rules.c
+ *
+ *
+ * Changes:
+ *
+ */
+#include <linux/config.h>
+#include <linux/string.h>
+#include <linux/net.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/init.h>
+#include <linux/skbuff.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <linux/proc_fs.h>
+#include <linux/netdevice.h>
+#include <linux/timer.h>
+#include <linux/spinlock.h>
+#include <linux/in_route.h>
+#include <asm/atomic.h>
+#include <asm/uaccess.h>
+#include <net/neighbour.h>
+#include <net/dst.h>
+#include <net/flow.h>
+#include <net/dn.h>
+#include <net/dn_fib.h>
+#include <net/dn_neigh.h>
+#include <net/dn_dev.h>
+
+struct dn_fib_rule
+{
+	struct dn_fib_rule	*r_next;
+	atomic_t		r_clntref;
+	u32			r_preference;
+	unsigned char		r_table;
+	unsigned char		r_action;
+	unsigned char		r_dst_len;
+	unsigned char		r_src_len;
+	dn_address		r_src;
+	dn_address		r_srcmask;
+	dn_address		r_dst;
+	dn_address		r_dstmask;
+	dn_address		r_srcmap;
+	u8			r_flags;
+#ifdef CONFIG_DECNET_ROUTE_FWMARK
+	u32			r_fwmark;
+#endif
+	int			r_ifindex;
+	char			r_ifname[IFNAMSIZ];
+	int			r_dead;
+};
+
+static struct dn_fib_rule default_rule = {
+	.r_clntref =		ATOMIC_INIT(2),
+	.r_preference =		0x7fff,
+	.r_table =		RT_TABLE_MAIN,
+	.r_action =		RTN_UNICAST
+};
+
+static struct dn_fib_rule *dn_fib_rules = &default_rule;
+static DEFINE_RWLOCK(dn_fib_rules_lock);
+
+
+int dn_fib_rtm_delrule(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
+{
+	struct rtattr **rta = arg;
+	struct rtmsg *rtm = NLMSG_DATA(nlh);
+	struct dn_fib_rule *r, **rp;
+	int err = -ESRCH;
+
+	for(rp=&dn_fib_rules; (r=*rp) != NULL; rp = &r->r_next) {
+		if ((!rta[RTA_SRC-1] || memcmp(RTA_DATA(rta[RTA_SRC-1]), &r->r_src, 2) == 0) &&
+			rtm->rtm_src_len == r->r_src_len &&
+			rtm->rtm_dst_len == r->r_dst_len &&
+			(!rta[RTA_DST-1] || memcmp(RTA_DATA(rta[RTA_DST-1]), &r->r_dst, 2) == 0) &&
+#ifdef CONFIG_DECNET_ROUTE_FWMARK
+			(!rta[RTA_PROTOINFO-1] || memcmp(RTA_DATA(rta[RTA_PROTOINFO-1]), &r->r_fwmark, 4) == 0) &&
+#endif
+			(!rtm->rtm_type || rtm->rtm_type == r->r_action) &&
+			(!rta[RTA_PRIORITY-1] || memcmp(RTA_DATA(rta[RTA_PRIORITY-1]), &r->r_preference, 4) == 0) &&
+			(!rta[RTA_IIF-1] || rtattr_strcmp(rta[RTA_IIF-1], r->r_ifname) == 0) &&
+			(!rtm->rtm_table || (r && rtm->rtm_table == r->r_table))) {
+
+			err = -EPERM;
+			if (r == &default_rule)
+				break;
+
+			write_lock_bh(&dn_fib_rules_lock);
+			*rp = r->r_next;
+			r->r_dead = 1;
+			write_unlock_bh(&dn_fib_rules_lock);
+			dn_fib_rule_put(r);
+			err = 0;
+			break;
+		}
+	}
+
+	return err;
+}
+
+void dn_fib_rule_put(struct dn_fib_rule *r)
+{
+	if (atomic_dec_and_test(&r->r_clntref)) {
+		if (r->r_dead)
+			kfree(r);
+		else
+			printk(KERN_DEBUG "Attempt to free alive dn_fib_rule\n");
+	}
+}
+
+
+int dn_fib_rtm_newrule(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
+{
+	struct rtattr **rta = arg;
+	struct rtmsg *rtm = NLMSG_DATA(nlh);
+	struct dn_fib_rule *r, *new_r, **rp;
+	unsigned char table_id;
+
+	if (rtm->rtm_src_len > 16 || rtm->rtm_dst_len > 16)
+		return -EINVAL;
+
+	if (rta[RTA_IIF-1] && RTA_PAYLOAD(rta[RTA_IIF-1]) > IFNAMSIZ)
+		return -EINVAL;
+
+	if (rtm->rtm_type == RTN_NAT)
+		return -EINVAL;
+
+	table_id = rtm->rtm_table;
+	if (table_id == RT_TABLE_UNSPEC) {
+		struct dn_fib_table *tb;
+		if (rtm->rtm_type == RTN_UNICAST) {
+			if ((tb = dn_fib_empty_table()) == NULL)
+				return -ENOBUFS;
+			table_id = tb->n;
+		}
+	}
+
+	new_r = kmalloc(sizeof(*new_r), GFP_KERNEL);
+	if (!new_r)
+		return -ENOMEM;
+	memset(new_r, 0, sizeof(*new_r));
+	if (rta[RTA_SRC-1])
+		memcpy(&new_r->r_src, RTA_DATA(rta[RTA_SRC-1]), 2);
+	if (rta[RTA_DST-1])
+		memcpy(&new_r->r_dst, RTA_DATA(rta[RTA_DST-1]), 2);
+	if (rta[RTA_GATEWAY-1])
+		memcpy(&new_r->r_srcmap, RTA_DATA(rta[RTA_GATEWAY-1]), 2);
+	new_r->r_src_len = rtm->rtm_src_len;
+	new_r->r_dst_len = rtm->rtm_dst_len;
+	new_r->r_srcmask = dnet_make_mask(rtm->rtm_src_len);
+	new_r->r_dstmask = dnet_make_mask(rtm->rtm_dst_len);
+#ifdef CONFIG_DECNET_ROUTE_FWMARK
+	if (rta[RTA_PROTOINFO-1])
+		memcpy(&new_r->r_fwmark, RTA_DATA(rta[RTA_PROTOINFO-1]), 4);
+#endif
+	new_r->r_action = rtm->rtm_type;
+	new_r->r_flags = rtm->rtm_flags;
+	if (rta[RTA_PRIORITY-1])
+		memcpy(&new_r->r_preference, RTA_DATA(rta[RTA_PRIORITY-1]), 4);
+	new_r->r_table = table_id;
+	if (rta[RTA_IIF-1]) {
+		struct net_device *dev;
+		rtattr_strlcpy(new_r->r_ifname, rta[RTA_IIF-1], IFNAMSIZ);
+		new_r->r_ifindex = -1;
+		dev = dev_get_by_name(new_r->r_ifname);
+		if (dev) {
+			new_r->r_ifindex = dev->ifindex;
+			dev_put(dev);
+		}
+	}
+
+	rp = &dn_fib_rules;
+	if (!new_r->r_preference) {
+		r = dn_fib_rules;
+		if (r && (r = r->r_next) != NULL) {
+			rp = &dn_fib_rules->r_next;
+			if (r->r_preference)
+				new_r->r_preference = r->r_preference - 1;
+		}
+	}
+
+	while((r=*rp) != NULL) {
+		if (r->r_preference > new_r->r_preference)
+			break;
+		rp = &r->r_next;
+	}
+
+	new_r->r_next = r;
+	atomic_inc(&new_r->r_clntref);
+	write_lock_bh(&dn_fib_rules_lock);
+	*rp = new_r;
+	write_unlock_bh(&dn_fib_rules_lock);
+	return 0;
+}
+
+
+int dn_fib_lookup(const struct flowi *flp, struct dn_fib_res *res)
+{
+	struct dn_fib_rule *r, *policy;
+	struct dn_fib_table *tb;
+	dn_address saddr = flp->fld_src;
+	dn_address daddr = flp->fld_dst;
+	int err;
+
+	read_lock(&dn_fib_rules_lock);
+	for(r = dn_fib_rules; r; r = r->r_next) {
+		if (((saddr^r->r_src) & r->r_srcmask) ||
+		    ((daddr^r->r_dst) & r->r_dstmask) ||
+#ifdef CONFIG_DECNET_ROUTE_FWMARK
+		    (r->r_fwmark && r->r_fwmark != flp->fld_fwmark) ||
+#endif
+		    (r->r_ifindex && r->r_ifindex != flp->iif))
+			continue;
+
+		switch(r->r_action) {
+			case RTN_UNICAST:
+			case RTN_NAT:
+				policy = r;
+				break;
+			case RTN_UNREACHABLE:
+				read_unlock(&dn_fib_rules_lock);
+				return -ENETUNREACH;
+			default:
+			case RTN_BLACKHOLE:
+				read_unlock(&dn_fib_rules_lock);
+				return -EINVAL;
+			case RTN_PROHIBIT:
+				read_unlock(&dn_fib_rules_lock);
+				return -EACCES;
+		}
+
+		if ((tb = dn_fib_get_table(r->r_table, 0)) == NULL)
+			continue;
+		err = tb->lookup(tb, flp, res);
+		if (err == 0) {
+			res->r = policy;
+			if (policy)
+				atomic_inc(&policy->r_clntref);
+			read_unlock(&dn_fib_rules_lock);
+			return 0;
+		}
+		if (err < 0 && err != -EAGAIN) {
+			read_unlock(&dn_fib_rules_lock);
+			return err;
+		}
+	}
+
+	read_unlock(&dn_fib_rules_lock);
+	return -ESRCH;
+}
+
+unsigned dnet_addr_type(__u16 addr)
+{
+	struct flowi fl = { .nl_u = { .dn_u = { .daddr = addr } } };
+	struct dn_fib_res res;
+	unsigned ret = RTN_UNICAST;
+	struct dn_fib_table *tb = dn_fib_tables[RT_TABLE_LOCAL];
+
+	res.r = NULL;
+
+	if (tb) {
+		if (!tb->lookup(tb, &fl, &res)) {
+			ret = res.type;
+			dn_fib_res_put(&res);
+		}
+	}
+	return ret;
+}
+
+__u16 dn_fib_rules_policy(__u16 saddr, struct dn_fib_res *res, unsigned *flags)
+{
+	struct dn_fib_rule *r = res->r;
+
+	if (r->r_action == RTN_NAT) {
+		int addrtype = dnet_addr_type(r->r_srcmap);
+
+		if (addrtype == RTN_NAT) {
+			saddr = (saddr&~r->r_srcmask)|r->r_srcmap;
+			*flags |= RTCF_SNAT;
+		} else if (addrtype == RTN_LOCAL || r->r_srcmap == 0) {
+			saddr = r->r_srcmap;
+			*flags |= RTCF_MASQ;
+		}
+	}
+	return saddr;
+}
+
+static void dn_fib_rules_detach(struct net_device *dev)
+{
+	struct dn_fib_rule *r;
+
+	for(r = dn_fib_rules; r; r = r->r_next) {
+		if (r->r_ifindex == dev->ifindex) {
+			write_lock_bh(&dn_fib_rules_lock);
+			r->r_ifindex = -1;
+			write_unlock_bh(&dn_fib_rules_lock);
+		}
+	}
+}
+
+static void dn_fib_rules_attach(struct net_device *dev)
+{
+	struct dn_fib_rule *r;
+
+	for(r = dn_fib_rules; r; r = r->r_next) {
+		if (r->r_ifindex == -1 && strcmp(dev->name, r->r_ifname) == 0) {
+			write_lock_bh(&dn_fib_rules_lock);
+			r->r_ifindex = dev->ifindex;
+			write_unlock_bh(&dn_fib_rules_lock);
+		}
+	}
+}
+
+static int dn_fib_rules_event(struct notifier_block *this, unsigned long event, void *ptr)
+{
+	struct net_device *dev = ptr;
+
+	switch(event) {
+		case NETDEV_UNREGISTER:
+			dn_fib_rules_detach(dev);
+			dn_fib_sync_down(0, dev, 1);
+		case NETDEV_REGISTER:
+			dn_fib_rules_attach(dev);
+			dn_fib_sync_up(dev);
+	}
+
+	return NOTIFY_DONE;
+}
+
+
+static struct notifier_block dn_fib_rules_notifier = {
+	.notifier_call =	dn_fib_rules_event,
+};
+
+static int dn_fib_fill_rule(struct sk_buff *skb, struct dn_fib_rule *r, struct netlink_callback *cb)
+{
+	struct rtmsg *rtm;
+	struct nlmsghdr *nlh;
+	unsigned char *b = skb->tail;
+
+
+	nlh = NLMSG_PUT(skb, NETLINK_CREDS(cb->skb)->pid, cb->nlh->nlmsg_seq, RTM_NEWRULE, sizeof(*rtm));
+	rtm = NLMSG_DATA(nlh);
+	rtm->rtm_family = AF_DECnet;
+	rtm->rtm_dst_len = r->r_dst_len;
+	rtm->rtm_src_len = r->r_src_len;
+	rtm->rtm_tos = 0;
+#ifdef CONFIG_DECNET_ROUTE_FWMARK
+	if (r->r_fwmark)
+		RTA_PUT(skb, RTA_PROTOINFO, 4, &r->r_fwmark);
+#endif
+	rtm->rtm_table = r->r_table;
+	rtm->rtm_protocol = 0;
+	rtm->rtm_scope = 0;
+	rtm->rtm_type = r->r_action;
+	rtm->rtm_flags = r->r_flags;
+
+	if (r->r_dst_len)
+		RTA_PUT(skb, RTA_DST, 2, &r->r_dst);
+	if (r->r_src_len)
+		RTA_PUT(skb, RTA_SRC, 2, &r->r_src);
+	if (r->r_ifname[0])
+		RTA_PUT(skb, RTA_IIF, IFNAMSIZ, &r->r_ifname);
+	if (r->r_preference)
+		RTA_PUT(skb, RTA_PRIORITY, 4, &r->r_preference);
+	if (r->r_srcmap)
+		RTA_PUT(skb, RTA_GATEWAY, 2, &r->r_srcmap);
+	nlh->nlmsg_len = skb->tail - b;
+	return skb->len;
+
+nlmsg_failure:
+rtattr_failure:
+	skb_trim(skb, b - skb->data);
+	return -1;
+}
+
+int dn_fib_dump_rules(struct sk_buff *skb, struct netlink_callback *cb)
+{
+	int idx;
+	int s_idx = cb->args[0];
+	struct dn_fib_rule *r;
+
+	read_lock(&dn_fib_rules_lock);
+	for(r = dn_fib_rules, idx = 0; r; r = r->r_next, idx++) {
+		if (idx < s_idx)
+			continue;
+		if (dn_fib_fill_rule(skb, r, cb) < 0)
+			break;
+	}
+	read_unlock(&dn_fib_rules_lock);
+	cb->args[0] = idx;
+
+	return skb->len;
+}
+
+void __init dn_fib_rules_init(void)
+{
+	register_netdevice_notifier(&dn_fib_rules_notifier);
+}
+
+void __exit dn_fib_rules_cleanup(void)
+{
+	unregister_netdevice_notifier(&dn_fib_rules_notifier);
+}
+
+
diff --git a/net/decnet/dn_table.c b/net/decnet/dn_table.c
new file mode 100644
index 0000000..dad5603
--- /dev/null
+++ b/net/decnet/dn_table.c
@@ -0,0 +1,825 @@
+/*
+ * DECnet       An implementation of the DECnet protocol suite for the LINUX
+ *              operating system.  DECnet is implemented using the  BSD Socket
+ *              interface as the means of communication with the user level.
+ *
+ *              DECnet Routing Forwarding Information Base (Routing Tables)
+ *
+ * Author:      Steve Whitehouse <SteveW@ACM.org>
+ *              Mostly copied from the IPv4 routing code
+ *
+ *
+ * Changes:
+ *
+ */
+#include <linux/config.h>
+#include <linux/string.h>
+#include <linux/net.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/init.h>
+#include <linux/skbuff.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <linux/proc_fs.h>
+#include <linux/netdevice.h>
+#include <linux/timer.h>
+#include <linux/spinlock.h>
+#include <asm/atomic.h>
+#include <asm/uaccess.h>
+#include <linux/route.h> /* RTF_xxx */
+#include <net/neighbour.h>
+#include <net/dst.h>
+#include <net/flow.h>
+#include <net/dn.h>
+#include <net/dn_route.h>
+#include <net/dn_fib.h>
+#include <net/dn_neigh.h>
+#include <net/dn_dev.h>
+
+struct dn_zone
+{
+	struct dn_zone		*dz_next;
+	struct dn_fib_node 	**dz_hash;
+	int			dz_nent;
+	int			dz_divisor;
+	u32			dz_hashmask;
+#define DZ_HASHMASK(dz)	((dz)->dz_hashmask)
+	int			dz_order;
+	u16			dz_mask;
+#define DZ_MASK(dz)	((dz)->dz_mask)
+};
+
+struct dn_hash
+{
+	struct dn_zone	*dh_zones[17];
+	struct dn_zone	*dh_zone_list;
+};
+
+#define dz_key_0(key)		((key).datum = 0)
+#define dz_prefix(key,dz)	((key).datum)
+
+#define for_nexthops(fi) { int nhsel; const struct dn_fib_nh *nh;\
+        for(nhsel = 0, nh = (fi)->fib_nh; nhsel < (fi)->fib_nhs; nh++, nhsel++)
+
+#define endfor_nexthops(fi) }
+
+#define DN_MAX_DIVISOR 1024
+#define DN_S_ZOMBIE 1
+#define DN_S_ACCESSED 2
+
+#define DN_FIB_SCAN(f, fp) \
+for( ; ((f) = *(fp)) != NULL; (fp) = &(f)->fn_next)
+
+#define DN_FIB_SCAN_KEY(f, fp, key) \
+for( ; ((f) = *(fp)) != NULL && dn_key_eq((f)->fn_key, (key)); (fp) = &(f)->fn_next)
+
+#define RT_TABLE_MIN 1
+
+static DEFINE_RWLOCK(dn_fib_tables_lock);
+struct dn_fib_table *dn_fib_tables[RT_TABLE_MAX + 1];
+
+static kmem_cache_t *dn_hash_kmem;
+static int dn_fib_hash_zombies;
+
+static inline dn_fib_idx_t dn_hash(dn_fib_key_t key, struct dn_zone *dz)
+{
+	u16 h = ntohs(key.datum)>>(16 - dz->dz_order);
+	h ^= (h >> 10);
+	h ^= (h >> 6);
+	h &= DZ_HASHMASK(dz);
+	return *(dn_fib_idx_t *)&h;
+}
+
+static inline dn_fib_key_t dz_key(u16 dst, struct dn_zone *dz)
+{
+	dn_fib_key_t k;
+	k.datum = dst & DZ_MASK(dz);
+	return k;
+}
+
+static inline struct dn_fib_node **dn_chain_p(dn_fib_key_t key, struct dn_zone *dz)
+{
+	return &dz->dz_hash[dn_hash(key, dz).datum];
+}
+
+static inline struct dn_fib_node *dz_chain(dn_fib_key_t key, struct dn_zone *dz)
+{
+	return dz->dz_hash[dn_hash(key, dz).datum];
+}
+
+static inline int dn_key_eq(dn_fib_key_t a, dn_fib_key_t b)
+{
+	return a.datum == b.datum;
+}
+
+static inline int dn_key_leq(dn_fib_key_t a, dn_fib_key_t b)
+{
+	return a.datum <= b.datum;
+}
+
+static inline void dn_rebuild_zone(struct dn_zone *dz,
+				   struct dn_fib_node **old_ht,
+				   int old_divisor)
+{
+	int i;
+	struct dn_fib_node *f, **fp, *next;
+
+	for(i = 0; i < old_divisor; i++) {
+		for(f = old_ht[i]; f; f = f->fn_next) {
+			next = f->fn_next;
+			for(fp = dn_chain_p(f->fn_key, dz);
+				*fp && dn_key_leq((*fp)->fn_key, f->fn_key);
+				fp = &(*fp)->fn_next)
+				/* NOTHING */;
+			f->fn_next = *fp;
+			*fp = f;
+		}
+	}
+}
+
+static void dn_rehash_zone(struct dn_zone *dz)
+{
+	struct dn_fib_node **ht, **old_ht;
+	int old_divisor, new_divisor;
+	u32 new_hashmask;
+
+	old_divisor = dz->dz_divisor;
+
+	switch(old_divisor) {
+		case 16:
+			new_divisor = 256;
+			new_hashmask = 0xFF;
+			break;
+		default:
+			printk(KERN_DEBUG "DECnet: dn_rehash_zone: BUG! %d\n", old_divisor);
+		case 256:
+			new_divisor = 1024;
+			new_hashmask = 0x3FF;
+			break;
+	}
+
+	ht = kmalloc(new_divisor*sizeof(struct dn_fib_node*), GFP_KERNEL);
+
+	if (ht == NULL)
+		return;
+
+	memset(ht, 0, new_divisor*sizeof(struct dn_fib_node *));
+	write_lock_bh(&dn_fib_tables_lock);
+	old_ht = dz->dz_hash;
+	dz->dz_hash = ht;
+	dz->dz_hashmask = new_hashmask;
+	dz->dz_divisor = new_divisor;
+	dn_rebuild_zone(dz, old_ht, old_divisor);
+	write_unlock_bh(&dn_fib_tables_lock);
+	kfree(old_ht);
+}
+
+static void dn_free_node(struct dn_fib_node *f)
+{
+	dn_fib_release_info(DN_FIB_INFO(f));
+	kmem_cache_free(dn_hash_kmem, f);
+}
+
+
+static struct dn_zone *dn_new_zone(struct dn_hash *table, int z)
+{
+	int i;
+	struct dn_zone *dz = kmalloc(sizeof(struct dn_zone), GFP_KERNEL);
+	if (!dz)
+		return NULL;
+
+	memset(dz, 0, sizeof(struct dn_zone));
+	if (z) {
+		dz->dz_divisor = 16;
+		dz->dz_hashmask = 0x0F;
+	} else {
+		dz->dz_divisor = 1;
+		dz->dz_hashmask = 0;
+	}
+
+	dz->dz_hash = kmalloc(dz->dz_divisor*sizeof(struct dn_fib_node *), GFP_KERNEL);
+
+	if (!dz->dz_hash) {
+		kfree(dz);
+		return NULL;
+	}
+
+	memset(dz->dz_hash, 0, dz->dz_divisor*sizeof(struct dn_fib_node*));
+	dz->dz_order = z;
+	dz->dz_mask = dnet_make_mask(z);
+
+	for(i = z + 1; i <= 16; i++)
+		if (table->dh_zones[i])
+			break;
+
+	write_lock_bh(&dn_fib_tables_lock);
+	if (i>16) {
+		dz->dz_next = table->dh_zone_list;
+		table->dh_zone_list = dz;
+	} else {
+		dz->dz_next = table->dh_zones[i]->dz_next;
+		table->dh_zones[i]->dz_next = dz;
+	}
+	table->dh_zones[z] = dz;
+	write_unlock_bh(&dn_fib_tables_lock);
+	return dz;
+}
+
+
+static int dn_fib_nh_match(struct rtmsg *r, struct nlmsghdr *nlh, struct dn_kern_rta *rta, struct dn_fib_info *fi)
+{
+	struct rtnexthop *nhp;
+	int nhlen;
+
+	if (rta->rta_priority && *rta->rta_priority != fi->fib_priority)
+		return 1;
+
+	if (rta->rta_oif || rta->rta_gw) {
+		if ((!rta->rta_oif || *rta->rta_oif == fi->fib_nh->nh_oif) &&
+		    (!rta->rta_gw  || memcmp(rta->rta_gw, &fi->fib_nh->nh_gw, 2) == 0))
+			return 0;
+		return 1;
+	}
+
+	if (rta->rta_mp == NULL)
+		return 0;
+
+	nhp = RTA_DATA(rta->rta_mp);
+	nhlen = RTA_PAYLOAD(rta->rta_mp);
+
+	for_nexthops(fi) {
+		int attrlen = nhlen - sizeof(struct rtnexthop);
+		dn_address gw;
+
+		if (attrlen < 0 || (nhlen -= nhp->rtnh_len) < 0)
+			return -EINVAL;
+		if (nhp->rtnh_ifindex && nhp->rtnh_ifindex != nh->nh_oif)
+			return 1;
+		if (attrlen) {
+			gw = dn_fib_get_attr16(RTNH_DATA(nhp), attrlen, RTA_GATEWAY);
+
+			if (gw && gw != nh->nh_gw)
+				return 1;
+		}
+		nhp = RTNH_NEXT(nhp);
+	} endfor_nexthops(fi);
+
+	return 0;
+}
+
+static int dn_fib_dump_info(struct sk_buff *skb, u32 pid, u32 seq, int event,
+                        u8 tb_id, u8 type, u8 scope, void *dst, int dst_len,
+                        struct dn_fib_info *fi)
+{
+        struct rtmsg *rtm;
+        struct nlmsghdr *nlh;
+        unsigned char *b = skb->tail;
+
+        nlh = NLMSG_PUT(skb, pid, seq, event, sizeof(*rtm));
+        rtm = NLMSG_DATA(nlh);
+        rtm->rtm_family = AF_DECnet;
+        rtm->rtm_dst_len = dst_len;
+        rtm->rtm_src_len = 0;
+        rtm->rtm_tos = 0;
+        rtm->rtm_table = tb_id;
+        rtm->rtm_flags = fi->fib_flags;
+        rtm->rtm_scope = scope;
+	rtm->rtm_type  = type;
+        if (rtm->rtm_dst_len)
+                RTA_PUT(skb, RTA_DST, 2, dst);
+        rtm->rtm_protocol = fi->fib_protocol;
+        if (fi->fib_priority)
+                RTA_PUT(skb, RTA_PRIORITY, 4, &fi->fib_priority);
+	if (rtnetlink_put_metrics(skb, fi->fib_metrics) < 0)
+		goto rtattr_failure;
+        if (fi->fib_nhs == 1) {
+                if (fi->fib_nh->nh_gw)
+                        RTA_PUT(skb, RTA_GATEWAY, 2, &fi->fib_nh->nh_gw);
+                if (fi->fib_nh->nh_oif)
+                        RTA_PUT(skb, RTA_OIF, sizeof(int), &fi->fib_nh->nh_oif);
+        }
+        if (fi->fib_nhs > 1) {
+                struct rtnexthop *nhp;
+                struct rtattr *mp_head;
+                if (skb_tailroom(skb) <= RTA_SPACE(0))
+                        goto rtattr_failure;
+                mp_head = (struct rtattr *)skb_put(skb, RTA_SPACE(0));
+
+                for_nexthops(fi) {
+                        if (skb_tailroom(skb) < RTA_ALIGN(RTA_ALIGN(sizeof(*nhp)) + 4))
+                                goto rtattr_failure;
+                        nhp = (struct rtnexthop *)skb_put(skb, RTA_ALIGN(sizeof(*nhp)));
+                        nhp->rtnh_flags = nh->nh_flags & 0xFF;
+                        nhp->rtnh_hops = nh->nh_weight - 1;
+                        nhp->rtnh_ifindex = nh->nh_oif;
+                        if (nh->nh_gw)
+                                RTA_PUT(skb, RTA_GATEWAY, 2, &nh->nh_gw);
+                        nhp->rtnh_len = skb->tail - (unsigned char *)nhp;
+                } endfor_nexthops(fi);
+                mp_head->rta_type = RTA_MULTIPATH;
+                mp_head->rta_len = skb->tail - (u8*)mp_head;
+        }
+
+        nlh->nlmsg_len = skb->tail - b;
+        return skb->len;
+
+
+nlmsg_failure:
+rtattr_failure:
+        skb_trim(skb, b - skb->data);
+        return -1;
+}
+
+
+static void dn_rtmsg_fib(int event, struct dn_fib_node *f, int z, int tb_id,
+                        struct nlmsghdr *nlh, struct netlink_skb_parms *req)
+{
+        struct sk_buff *skb;
+        u32 pid = req ? req->pid : 0;
+        int size = NLMSG_SPACE(sizeof(struct rtmsg) + 256);
+
+        skb = alloc_skb(size, GFP_KERNEL);
+        if (!skb)
+                return;
+
+        if (dn_fib_dump_info(skb, pid, nlh->nlmsg_seq, event, tb_id, 
+                                f->fn_type, f->fn_scope, &f->fn_key, z, 
+                                DN_FIB_INFO(f)) < 0) {
+                kfree_skb(skb);
+                return;
+        }
+        NETLINK_CB(skb).dst_groups = RTMGRP_DECnet_ROUTE;
+        if (nlh->nlmsg_flags & NLM_F_ECHO)
+                atomic_inc(&skb->users);
+        netlink_broadcast(rtnl, skb, pid, RTMGRP_DECnet_ROUTE, GFP_KERNEL);
+        if (nlh->nlmsg_flags & NLM_F_ECHO)
+                netlink_unicast(rtnl, skb, pid, MSG_DONTWAIT);
+}
+
+static __inline__ int dn_hash_dump_bucket(struct sk_buff *skb, 
+				struct netlink_callback *cb,
+				struct dn_fib_table *tb,
+				struct dn_zone *dz,
+				struct dn_fib_node *f)
+{
+	int i, s_i;
+
+	s_i = cb->args[3];
+	for(i = 0; f; i++, f = f->fn_next) {
+		if (i < s_i)
+			continue;
+		if (f->fn_state & DN_S_ZOMBIE)
+			continue;
+		if (dn_fib_dump_info(skb, NETLINK_CB(cb->skb).pid, 
+				cb->nlh->nlmsg_seq,
+				RTM_NEWROUTE,
+				tb->n, 
+				(f->fn_state & DN_S_ZOMBIE) ? 0 : f->fn_type,
+				f->fn_scope, &f->fn_key, dz->dz_order, 
+				f->fn_info) < 0) {
+			cb->args[3] = i;
+			return -1;
+		}
+	}
+	cb->args[3] = i;
+	return skb->len;
+}
+
+static __inline__ int dn_hash_dump_zone(struct sk_buff *skb, 
+				struct netlink_callback *cb,
+				struct dn_fib_table *tb,
+				struct dn_zone *dz)
+{
+	int h, s_h;
+
+	s_h = cb->args[2];
+	for(h = 0; h < dz->dz_divisor; h++) {
+		if (h < s_h)
+			continue;
+		if (h > s_h)
+			memset(&cb->args[3], 0, sizeof(cb->args) - 3*sizeof(cb->args[0]));
+		if (dz->dz_hash == NULL || dz->dz_hash[h] == NULL)
+			continue;
+		if (dn_hash_dump_bucket(skb, cb, tb, dz, dz->dz_hash[h]) < 0) {
+			cb->args[2] = h;
+			return -1;
+		}
+	}
+	cb->args[2] = h;
+	return skb->len;
+}
+
+static int dn_fib_table_dump(struct dn_fib_table *tb, struct sk_buff *skb, 
+                                struct netlink_callback *cb)
+{
+        int m, s_m;
+	struct dn_zone *dz;
+	struct dn_hash *table = (struct dn_hash *)tb->data;
+
+	s_m = cb->args[1];
+	read_lock(&dn_fib_tables_lock);
+	for(dz = table->dh_zone_list, m = 0; dz; dz = dz->dz_next, m++) {
+		if (m < s_m)
+			continue;
+		if (m > s_m)
+			memset(&cb->args[2], 0, sizeof(cb->args) - 2*sizeof(cb->args[0]));
+
+		if (dn_hash_dump_zone(skb, cb, tb, dz) < 0) {
+			cb->args[1] = m;
+			read_unlock(&dn_fib_tables_lock);
+			return -1;
+		}
+	}
+	read_unlock(&dn_fib_tables_lock);
+	cb->args[1] = m;
+
+        return skb->len;
+}
+
+static int dn_fib_table_insert(struct dn_fib_table *tb, struct rtmsg *r, struct dn_kern_rta *rta, struct nlmsghdr *n, struct netlink_skb_parms *req)
+{
+	struct dn_hash *table = (struct dn_hash *)tb->data;
+	struct dn_fib_node *new_f, *f, **fp, **del_fp;
+	struct dn_zone *dz;
+	struct dn_fib_info *fi;
+        int z = r->rtm_dst_len;
+	int type = r->rtm_type;
+	dn_fib_key_t key;
+        int err;
+
+        if (z > 16)
+                return -EINVAL;
+
+	dz = table->dh_zones[z];
+	if (!dz && !(dz = dn_new_zone(table, z)))
+		return -ENOBUFS;
+
+	dz_key_0(key);
+	if (rta->rta_dst) {
+		dn_address dst;
+		memcpy(&dst, rta->rta_dst, 2);
+		if (dst & ~DZ_MASK(dz))
+			return -EINVAL;
+		key = dz_key(dst, dz);
+	}
+
+        if ((fi = dn_fib_create_info(r, rta, n, &err)) == NULL)
+                return err;
+
+	if (dz->dz_nent > (dz->dz_divisor << 2) &&
+			dz->dz_divisor > DN_MAX_DIVISOR &&
+			(z==16 || (1<<z) > dz->dz_divisor))
+		dn_rehash_zone(dz);
+
+	fp = dn_chain_p(key, dz);
+
+	DN_FIB_SCAN(f, fp) {
+		if (dn_key_leq(key, f->fn_key))
+			break;
+	}
+
+	del_fp = NULL;
+
+	if (f && (f->fn_state & DN_S_ZOMBIE) &&
+			dn_key_eq(f->fn_key, key)) {
+		del_fp = fp;
+		fp = &f->fn_next;
+		f = *fp;
+		goto create;
+	}
+
+	DN_FIB_SCAN_KEY(f, fp, key) {
+		if (fi->fib_priority <= DN_FIB_INFO(f)->fib_priority)
+			break;
+	}
+
+	if (f && dn_key_eq(f->fn_key, key) &&
+			fi->fib_priority == DN_FIB_INFO(f)->fib_priority) {
+		struct dn_fib_node **ins_fp;
+
+		err = -EEXIST;
+		if (n->nlmsg_flags & NLM_F_EXCL)
+			goto out;
+
+		if (n->nlmsg_flags & NLM_F_REPLACE) {
+			del_fp = fp;
+			fp = &f->fn_next;
+			f = *fp;
+			goto replace;
+		}
+
+		ins_fp = fp;
+		err = -EEXIST;
+
+		DN_FIB_SCAN_KEY(f, fp, key) {
+			if (fi->fib_priority != DN_FIB_INFO(f)->fib_priority)
+				break;
+			if (f->fn_type == type && f->fn_scope == r->rtm_scope
+					&& DN_FIB_INFO(f) == fi)
+				goto out;
+		}
+
+		if (!(n->nlmsg_flags & NLM_F_APPEND)) {
+			fp = ins_fp;
+			f = *fp;
+		}
+	}
+
+create:
+	err = -ENOENT;
+	if (!(n->nlmsg_flags & NLM_F_CREATE))
+		goto out;
+
+replace:
+	err = -ENOBUFS;
+	new_f = kmem_cache_alloc(dn_hash_kmem, SLAB_KERNEL);
+	if (new_f == NULL)
+		goto out;
+
+	memset(new_f, 0, sizeof(struct dn_fib_node));
+
+	new_f->fn_key = key;
+	new_f->fn_type = type;
+	new_f->fn_scope = r->rtm_scope;
+	DN_FIB_INFO(new_f) = fi;
+
+	new_f->fn_next = f;
+	write_lock_bh(&dn_fib_tables_lock);
+	*fp = new_f;
+	write_unlock_bh(&dn_fib_tables_lock);
+	dz->dz_nent++;
+
+	if (del_fp) {
+		f = *del_fp;
+		write_lock_bh(&dn_fib_tables_lock);
+		*del_fp = f->fn_next;
+		write_unlock_bh(&dn_fib_tables_lock);
+
+		if (!(f->fn_state & DN_S_ZOMBIE))
+			dn_rtmsg_fib(RTM_DELROUTE, f, z, tb->n, n, req);
+		if (f->fn_state & DN_S_ACCESSED)
+			dn_rt_cache_flush(-1);
+		dn_free_node(f);
+		dz->dz_nent--;
+	} else {
+		dn_rt_cache_flush(-1);
+	}
+
+        dn_rtmsg_fib(RTM_NEWROUTE, new_f, z, tb->n, n, req);
+
+        return 0;
+out:
+	dn_fib_release_info(fi);
+	return err;
+}
+
+
+static int dn_fib_table_delete(struct dn_fib_table *tb, struct rtmsg *r, struct dn_kern_rta *rta, struct nlmsghdr *n, struct netlink_skb_parms *req)
+{
+	struct dn_hash *table = (struct dn_hash*)tb->data;
+	struct dn_fib_node **fp, **del_fp, *f;
+        int z = r->rtm_dst_len;
+	struct dn_zone *dz;
+	dn_fib_key_t key;
+	int matched;
+
+
+        if (z > 16)
+                return -EINVAL;
+
+	if ((dz = table->dh_zones[z]) == NULL)
+		return -ESRCH;
+
+	dz_key_0(key);
+	if (rta->rta_dst) {
+		dn_address dst;
+		memcpy(&dst, rta->rta_dst, 2);
+		if (dst & ~DZ_MASK(dz))
+			return -EINVAL;
+		key = dz_key(dst, dz);
+	}
+
+	fp = dn_chain_p(key, dz);
+
+	DN_FIB_SCAN(f, fp) {
+		if (dn_key_eq(f->fn_key, key))
+			break;
+		if (dn_key_leq(key, f->fn_key))
+			return -ESRCH;
+	}
+
+	matched = 0;
+	del_fp = NULL;
+	DN_FIB_SCAN_KEY(f, fp, key) {
+		struct dn_fib_info *fi = DN_FIB_INFO(f);
+
+		if (f->fn_state & DN_S_ZOMBIE)
+			return -ESRCH;
+
+		matched++;
+
+		if (del_fp == NULL &&
+				(!r->rtm_type || f->fn_type == r->rtm_type) &&
+				(r->rtm_scope == RT_SCOPE_NOWHERE || f->fn_scope == r->rtm_scope) &&
+				(!r->rtm_protocol || 
+					fi->fib_protocol == r->rtm_protocol) &&
+				dn_fib_nh_match(r, n, rta, fi) == 0)
+			del_fp = fp;
+	}
+
+	if (del_fp) {
+		f = *del_fp;
+        	dn_rtmsg_fib(RTM_DELROUTE, f, z, tb->n, n, req);
+
+		if (matched != 1) {
+			write_lock_bh(&dn_fib_tables_lock);
+			*del_fp = f->fn_next;
+			write_unlock_bh(&dn_fib_tables_lock);
+
+			if (f->fn_state & DN_S_ACCESSED)
+				dn_rt_cache_flush(-1);
+			dn_free_node(f);
+			dz->dz_nent--;
+		} else {
+			f->fn_state |= DN_S_ZOMBIE;
+			if (f->fn_state & DN_S_ACCESSED) {
+				f->fn_state &= ~DN_S_ACCESSED;
+				dn_rt_cache_flush(-1);
+			}
+			if (++dn_fib_hash_zombies > 128)
+				dn_fib_flush();
+		}
+
+		return 0;
+	}
+
+        return -ESRCH;
+}
+
+static inline int dn_flush_list(struct dn_fib_node **fp, int z, struct dn_hash *table)
+{
+	int found = 0;
+	struct dn_fib_node *f;
+
+	while((f = *fp) != NULL) {
+		struct dn_fib_info *fi = DN_FIB_INFO(f);
+
+		if (fi && ((f->fn_state & DN_S_ZOMBIE) || (fi->fib_flags & RTNH_F_DEAD))) {
+			write_lock_bh(&dn_fib_tables_lock);
+			*fp = f->fn_next;
+			write_unlock_bh(&dn_fib_tables_lock);
+
+			dn_free_node(f);
+			found++;
+			continue;
+		}
+		fp = &f->fn_next;
+	}
+
+	return found;
+}
+
+static int dn_fib_table_flush(struct dn_fib_table *tb)
+{
+	struct dn_hash *table = (struct dn_hash *)tb->data;
+	struct dn_zone *dz;
+	int found = 0;
+
+	dn_fib_hash_zombies = 0;
+	for(dz = table->dh_zone_list; dz; dz = dz->dz_next) {
+		int i;
+		int tmp = 0;
+		for(i = dz->dz_divisor-1; i >= 0; i--)
+			tmp += dn_flush_list(&dz->dz_hash[i], dz->dz_order, table);
+		dz->dz_nent -= tmp;
+		found += tmp;
+	}
+
+	return found;
+}
+
+static int dn_fib_table_lookup(struct dn_fib_table *tb, const struct flowi *flp, struct dn_fib_res *res)
+{
+        int err;
+	struct dn_zone *dz;
+	struct dn_hash *t = (struct dn_hash *)tb->data;
+
+	read_lock(&dn_fib_tables_lock);
+	for(dz = t->dh_zone_list; dz; dz = dz->dz_next) {
+		struct dn_fib_node *f;
+		dn_fib_key_t k = dz_key(flp->fld_dst, dz);
+
+		for(f = dz_chain(k, dz); f; f = f->fn_next) {
+			if (!dn_key_eq(k, f->fn_key)) {
+				if (dn_key_leq(k, f->fn_key))
+					break;
+				else
+					continue;
+			}
+
+			f->fn_state |= DN_S_ACCESSED;
+
+			if (f->fn_state&DN_S_ZOMBIE)
+				continue;
+
+			if (f->fn_scope < flp->fld_scope)
+				continue;
+
+			err = dn_fib_semantic_match(f->fn_type, DN_FIB_INFO(f), flp, res);
+
+			if (err == 0) {
+				res->type = f->fn_type;
+				res->scope = f->fn_scope; 
+				res->prefixlen = dz->dz_order;
+				goto out;
+			}
+			if (err < 0)
+				goto out;
+		}
+	}
+	err = 1;
+out:
+	read_unlock(&dn_fib_tables_lock);
+        return err;
+}
+
+
+struct dn_fib_table *dn_fib_get_table(int n, int create)
+{
+        struct dn_fib_table *t;
+
+        if (n < RT_TABLE_MIN)
+                return NULL;
+
+        if (n > RT_TABLE_MAX)
+                return NULL;
+
+        if (dn_fib_tables[n]) 
+                return dn_fib_tables[n];
+
+        if (!create)
+                return NULL;
+
+        if (in_interrupt() && net_ratelimit()) {
+                printk(KERN_DEBUG "DECnet: BUG! Attempt to create routing table from interrupt\n"); 
+                return NULL;
+        }
+        if ((t = kmalloc(sizeof(struct dn_fib_table) + sizeof(struct dn_hash), GFP_KERNEL)) == NULL)
+                return NULL;
+
+        memset(t, 0, sizeof(struct dn_fib_table));
+
+        t->n = n;
+        t->insert = dn_fib_table_insert;
+        t->delete = dn_fib_table_delete;
+        t->lookup = dn_fib_table_lookup;
+        t->flush  = dn_fib_table_flush;
+        t->dump = dn_fib_table_dump;
+	memset(t->data, 0, sizeof(struct dn_hash));
+        dn_fib_tables[n] = t;
+
+        return t;
+}
+
+static void dn_fib_del_tree(int n)
+{
+        struct dn_fib_table *t;
+
+        write_lock(&dn_fib_tables_lock);
+        t = dn_fib_tables[n];
+        dn_fib_tables[n] = NULL;
+        write_unlock(&dn_fib_tables_lock);
+
+        if (t) {
+                kfree(t);
+        }
+}
+
+struct dn_fib_table *dn_fib_empty_table(void)
+{
+        int id;
+
+        for(id = RT_TABLE_MIN; id <= RT_TABLE_MAX; id++)
+                if (dn_fib_tables[id] == NULL)
+                        return dn_fib_get_table(id, 1);
+        return NULL;
+}
+
+void __init dn_fib_table_init(void)
+{
+	dn_hash_kmem = kmem_cache_create("dn_fib_info_cache",
+					sizeof(struct dn_fib_info),
+					0, SLAB_HWCACHE_ALIGN,
+					NULL, NULL);
+}
+
+void __exit dn_fib_table_cleanup(void)
+{
+	int i;
+
+	for (i = RT_TABLE_MIN; i <= RT_TABLE_MAX; ++i)
+		dn_fib_del_tree(i);
+
+	return;
+}
diff --git a/net/decnet/dn_timer.c b/net/decnet/dn_timer.c
new file mode 100644
index 0000000..0982571
--- /dev/null
+++ b/net/decnet/dn_timer.c
@@ -0,0 +1,109 @@
+/*
+ * DECnet       An implementation of the DECnet protocol suite for the LINUX
+ *              operating system.  DECnet is implemented using the  BSD Socket
+ *              interface as the means of communication with the user level.
+ *
+ *              DECnet Socket Timer Functions
+ *
+ * Author:      Steve Whitehouse <SteveW@ACM.org>
+ *
+ *
+ * Changes:
+ *       Steve Whitehouse      : Made keepalive timer part of the same
+ *                               timer idea.
+ *       Steve Whitehouse      : Added checks for sk->sock_readers
+ *       David S. Miller       : New socket locking
+ *       Steve Whitehouse      : Timer grabs socket ref.
+ */
+#include <linux/net.h>
+#include <linux/socket.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/timer.h>
+#include <linux/spinlock.h>
+#include <net/sock.h>
+#include <asm/atomic.h>
+#include <net/flow.h>
+#include <net/dn.h>
+
+/*
+ * Slow timer is for everything else (n * 500mS)
+ */
+
+#define SLOW_INTERVAL (HZ/2)
+
+static void dn_slow_timer(unsigned long arg);
+
+void dn_start_slow_timer(struct sock *sk)
+{
+	sk->sk_timer.expires	= jiffies + SLOW_INTERVAL;
+	sk->sk_timer.function	= dn_slow_timer;
+	sk->sk_timer.data	= (unsigned long)sk;
+
+	add_timer(&sk->sk_timer);
+}
+
+void dn_stop_slow_timer(struct sock *sk)
+{
+	del_timer(&sk->sk_timer);
+}
+
+static void dn_slow_timer(unsigned long arg)
+{
+	struct sock *sk = (struct sock *)arg;
+	struct dn_scp *scp = DN_SK(sk);
+
+	sock_hold(sk);
+	bh_lock_sock(sk);
+
+	if (sock_owned_by_user(sk)) {
+		sk->sk_timer.expires = jiffies + HZ / 10;
+		add_timer(&sk->sk_timer);
+		goto out;
+	}
+
+	/*
+	 * The persist timer is the standard slow timer used for retransmits
+	 * in both connection establishment and disconnection as well as
+	 * in the RUN state. The different states are catered for by changing
+	 * the function pointer in the socket. Setting the timer to a value
+	 * of zero turns it off. We allow the persist_fxn to turn the
+	 * timer off in a permant way by returning non-zero, so that
+	 * timer based routines may remove sockets. This is why we have a
+	 * sock_hold()/sock_put() around the timer to prevent the socket
+	 * going away in the middle.
+	 */
+	if (scp->persist && scp->persist_fxn) {
+		if (scp->persist <= SLOW_INTERVAL) {
+			scp->persist = 0;
+
+			if (scp->persist_fxn(sk))
+				goto out;
+		} else {
+			scp->persist -= SLOW_INTERVAL;
+		}
+	}
+
+	/*
+	 * Check for keepalive timeout. After the other timer 'cos if
+	 * the previous timer caused a retransmit, we don't need to
+	 * do this. scp->stamp is the last time that we sent a packet.
+	 * The keepalive function sends a link service packet to the
+	 * other end. If it remains unacknowledged, the standard
+	 * socket timers will eventually shut the socket down. Each
+	 * time we do this, scp->stamp will be updated, thus
+	 * we won't try and send another until scp->keepalive has passed
+	 * since the last successful transmission.
+	 */
+	if (scp->keepalive && scp->keepalive_fxn && (scp->state == DN_RUN)) {
+		if ((jiffies - scp->stamp) >= scp->keepalive)
+			scp->keepalive_fxn(sk);
+	}
+
+	sk->sk_timer.expires = jiffies + SLOW_INTERVAL;
+
+	add_timer(&sk->sk_timer);
+out:
+	bh_unlock_sock(sk);
+	sock_put(sk);
+}
diff --git a/net/decnet/netfilter/Kconfig b/net/decnet/netfilter/Kconfig
new file mode 100644
index 0000000..ecdb3f9
--- /dev/null
+++ b/net/decnet/netfilter/Kconfig
@@ -0,0 +1,15 @@
+#
+# DECnet netfilter configuration
+#
+
+menu "DECnet: Netfilter Configuration"
+	depends on DECNET && NETFILTER && EXPERIMENTAL
+
+config DECNET_NF_GRABULATOR
+	tristate "Routing message grabulator (for userland routing daemon)"
+	help
+	  Enable this module if you want to use the userland DECnet routing
+	  daemon. You will also need to enable routing support for DECnet
+	  unless you just want to monitor routing messages from other nodes.
+
+endmenu
diff --git a/net/decnet/netfilter/Makefile b/net/decnet/netfilter/Makefile
new file mode 100644
index 0000000..255c1ae
--- /dev/null
+++ b/net/decnet/netfilter/Makefile
@@ -0,0 +1,6 @@
+#
+# Makefile for DECnet netfilter modules
+#
+
+obj-$(CONFIG_DECNET_NF_GRABULATOR) += dn_rtmsg.o
+
diff --git a/net/decnet/netfilter/dn_rtmsg.c b/net/decnet/netfilter/dn_rtmsg.c
new file mode 100644
index 0000000..f86a625
--- /dev/null
+++ b/net/decnet/netfilter/dn_rtmsg.c
@@ -0,0 +1,167 @@
+/*
+ * DECnet       An implementation of the DECnet protocol suite for the LINUX
+ *              operating system.  DECnet is implemented using the  BSD Socket
+ *              interface as the means of communication with the user level.
+ *
+ *              DECnet Routing Message Grabulator
+ *
+ *              (C) 2000 ChyGwyn Limited  -  http://www.chygwyn.com/
+ *              This code may be copied under the GPL v.2 or at your option
+ *              any later version.
+ *
+ * Author:      Steven Whitehouse <steve@chygwyn.com>
+ *
+ */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/init.h>
+#include <linux/netdevice.h>
+#include <linux/netfilter.h>
+#include <linux/spinlock.h>
+#include <linux/netlink.h>
+
+#include <net/sock.h>
+#include <net/flow.h>
+#include <net/dn.h>
+#include <net/dn_route.h>
+
+#include <linux/netfilter_decnet.h>
+
+static struct sock *dnrmg = NULL;
+
+
+static struct sk_buff *dnrmg_build_message(struct sk_buff *rt_skb, int *errp)
+{
+	struct sk_buff *skb = NULL;
+	size_t size;
+	unsigned char *old_tail;
+	struct nlmsghdr *nlh;
+	unsigned char *ptr;
+	struct nf_dn_rtmsg *rtm;
+
+	size = NLMSG_SPACE(rt_skb->len);
+	size += NLMSG_ALIGN(sizeof(struct nf_dn_rtmsg));
+	skb = alloc_skb(size, GFP_ATOMIC);
+	if (!skb)
+		goto nlmsg_failure;
+	old_tail = skb->tail;
+	nlh = NLMSG_PUT(skb, 0, 0, 0, size - sizeof(*nlh));
+	rtm = (struct nf_dn_rtmsg *)NLMSG_DATA(nlh);
+	rtm->nfdn_ifindex = rt_skb->dev->ifindex;
+	ptr = NFDN_RTMSG(rtm);
+	memcpy(ptr, rt_skb->data, rt_skb->len);
+	nlh->nlmsg_len = skb->tail - old_tail;
+	return skb;
+
+nlmsg_failure:
+	if (skb)
+		kfree_skb(skb);
+	*errp = -ENOMEM;
+	if (net_ratelimit())
+		printk(KERN_ERR "dn_rtmsg: error creating netlink message\n");
+	return NULL;
+}
+
+static void dnrmg_send_peer(struct sk_buff *skb)
+{
+	struct sk_buff *skb2;
+	int status = 0;
+	int group = 0;
+	unsigned char flags = *skb->data;
+
+	switch(flags & DN_RT_CNTL_MSK) {
+		case DN_RT_PKT_L1RT:
+			group = DNRMG_L1_GROUP;
+			break;
+		case DN_RT_PKT_L2RT:
+			group = DNRMG_L2_GROUP;
+			break;
+		default:
+			return;
+	}
+
+	skb2 = dnrmg_build_message(skb, &status);
+	if (skb2 == NULL)
+		return;
+	NETLINK_CB(skb2).dst_groups = group;
+	netlink_broadcast(dnrmg, skb2, 0, group, GFP_ATOMIC);
+}
+
+
+static unsigned int dnrmg_hook(unsigned int hook,
+			struct sk_buff **pskb,
+			const struct net_device *in,
+			const struct net_device *out,
+			int (*okfn)(struct sk_buff *))
+{
+	dnrmg_send_peer(*pskb);
+	return NF_ACCEPT;
+}
+
+
+#define RCV_SKB_FAIL(err) do { netlink_ack(skb, nlh, (err)); return; } while (0)
+
+static inline void dnrmg_receive_user_skb(struct sk_buff *skb)
+{
+	struct nlmsghdr *nlh = (struct nlmsghdr *)skb->data;
+
+	if (nlh->nlmsg_len < sizeof(*nlh) || skb->len < nlh->nlmsg_len)
+		return;
+
+	if (!cap_raised(NETLINK_CB(skb).eff_cap, CAP_NET_ADMIN))
+		RCV_SKB_FAIL(-EPERM);
+
+	/* Eventually we might send routing messages too */
+
+	RCV_SKB_FAIL(-EINVAL);
+}
+
+static void dnrmg_receive_user_sk(struct sock *sk, int len)
+{
+	struct sk_buff *skb;
+
+	while((skb = skb_dequeue(&sk->sk_receive_queue)) != NULL) {
+		dnrmg_receive_user_skb(skb);
+		kfree_skb(skb);
+	}
+}
+
+static struct nf_hook_ops dnrmg_ops = {
+	.hook		= dnrmg_hook,
+	.pf		= PF_DECnet,
+	.hooknum	= NF_DN_ROUTE,
+	.priority	= NF_DN_PRI_DNRTMSG,
+};
+
+static int __init init(void)
+{
+	int rv = 0;
+
+	dnrmg = netlink_kernel_create(NETLINK_DNRTMSG, dnrmg_receive_user_sk);
+	if (dnrmg == NULL) {
+		printk(KERN_ERR "dn_rtmsg: Cannot create netlink socket");
+		return -ENOMEM;
+	}
+
+	rv = nf_register_hook(&dnrmg_ops);
+	if (rv) {
+		sock_release(dnrmg->sk_socket);
+	}
+
+	return rv;
+}
+
+static void __exit fini(void)
+{
+	nf_unregister_hook(&dnrmg_ops);
+	sock_release(dnrmg->sk_socket);
+}
+
+
+MODULE_DESCRIPTION("DECnet Routing Message Grabulator");
+MODULE_AUTHOR("Steven Whitehouse <steve@chygwyn.com>");
+MODULE_LICENSE("GPL");
+
+module_init(init);
+module_exit(fini);
+
diff --git a/net/decnet/sysctl_net_decnet.c b/net/decnet/sysctl_net_decnet.c
new file mode 100644
index 0000000..02bca49
--- /dev/null
+++ b/net/decnet/sysctl_net_decnet.c
@@ -0,0 +1,480 @@
+/*
+ * DECnet       An implementation of the DECnet protocol suite for the LINUX
+ *              operating system.  DECnet is implemented using the  BSD Socket
+ *              interface as the means of communication with the user level.
+ *
+ *              DECnet sysctl support functions
+ *
+ * Author:      Steve Whitehouse <SteveW@ACM.org>
+ *
+ *
+ * Changes:
+ * Steve Whitehouse - C99 changes and default device handling
+ *
+ */
+#include <linux/config.h>
+#include <linux/mm.h>
+#include <linux/sysctl.h>
+#include <linux/fs.h>
+#include <linux/netdevice.h>
+#include <linux/string.h>
+#include <net/neighbour.h>
+#include <net/dst.h>
+#include <net/flow.h>
+
+#include <asm/uaccess.h>
+
+#include <net/dn.h>
+#include <net/dn_dev.h>
+#include <net/dn_route.h>
+
+
+int decnet_debug_level;
+int decnet_time_wait = 30;
+int decnet_dn_count = 1;
+int decnet_di_count = 3;
+int decnet_dr_count = 3;
+int decnet_log_martians = 1;
+int decnet_no_fc_max_cwnd = NSP_MIN_WINDOW;
+
+#ifdef CONFIG_SYSCTL
+extern int decnet_dst_gc_interval;
+static int min_decnet_time_wait[] = { 5 };
+static int max_decnet_time_wait[] = { 600 };
+static int min_state_count[] = { 1 };
+static int max_state_count[] = { NSP_MAXRXTSHIFT };
+static int min_decnet_dst_gc_interval[] = { 1 };
+static int max_decnet_dst_gc_interval[] = { 60 };
+static int min_decnet_no_fc_max_cwnd[] = { NSP_MIN_WINDOW };
+static int max_decnet_no_fc_max_cwnd[] = { NSP_MAX_WINDOW };
+static char node_name[7] = "???";
+
+static struct ctl_table_header *dn_table_header = NULL;
+
+/*
+ * ctype.h :-)
+ */
+#define ISNUM(x) (((x) >= '0') && ((x) <= '9'))
+#define ISLOWER(x) (((x) >= 'a') && ((x) <= 'z'))
+#define ISUPPER(x) (((x) >= 'A') && ((x) <= 'Z'))
+#define ISALPHA(x) (ISLOWER(x) || ISUPPER(x))
+#define INVALID_END_CHAR(x) (ISNUM(x) || ISALPHA(x))
+
+static void strip_it(char *str)
+{
+	for(;;) {
+		switch(*str) {
+			case ' ':
+			case '\n':
+			case '\r':
+			case ':':
+				*str = 0;
+			case 0:
+				return;
+		}
+		str++;
+	}
+}
+
+/*
+ * Simple routine to parse an ascii DECnet address
+ * into a network order address.
+ */
+static int parse_addr(dn_address *addr, char *str)
+{
+	dn_address area, node;
+
+	while(*str && !ISNUM(*str)) str++;
+
+	if (*str == 0)
+		return -1;
+
+	area = (*str++ - '0');
+	if (ISNUM(*str)) {
+		area *= 10;
+		area += (*str++ - '0');
+	}
+
+	if (*str++ != '.')
+		return -1;
+
+	if (!ISNUM(*str))
+		return -1;
+
+	node = *str++ - '0';
+	if (ISNUM(*str)) {
+		node *= 10;
+		node += (*str++ - '0');
+	}
+	if (ISNUM(*str)) {
+		node *= 10;
+		node += (*str++ - '0');
+	}
+	if (ISNUM(*str)) {
+		node *= 10;
+		node += (*str++ - '0');
+	}
+
+	if ((node > 1023) || (area > 63))
+		return -1;
+
+	if (INVALID_END_CHAR(*str))
+		return -1;
+
+	*addr = dn_htons((area << 10) | node);
+
+	return 0;
+}
+
+
+static int dn_node_address_strategy(ctl_table *table, int __user *name, int nlen,
+				void __user *oldval, size_t __user *oldlenp,
+				void __user *newval, size_t newlen,
+				void **context)
+{
+	size_t len;
+	dn_address addr;
+
+	if (oldval && oldlenp) {
+		if (get_user(len, oldlenp))
+			return -EFAULT;
+		if (len) {
+			if (len != sizeof(unsigned short))
+				return -EINVAL;
+			if (put_user(decnet_address, (unsigned short __user *)oldval))
+				return -EFAULT;
+		}
+	}
+	if (newval && newlen) {
+		if (newlen != sizeof(unsigned short))
+			return -EINVAL;
+		if (get_user(addr, (unsigned short __user *)newval))
+			return -EFAULT;
+
+		dn_dev_devices_off();
+
+		decnet_address = addr;
+
+		dn_dev_devices_on();
+	}
+	return 0;
+}
+
+static int dn_node_address_handler(ctl_table *table, int write, 
+				struct file *filp,
+				void __user *buffer,
+				size_t *lenp, loff_t *ppos)
+{
+	char addr[DN_ASCBUF_LEN];
+	size_t len;
+	dn_address dnaddr;
+
+	if (!*lenp || (*ppos && !write)) {
+		*lenp = 0;
+		return 0;
+	}
+
+	if (write) {
+		int len = (*lenp < DN_ASCBUF_LEN) ? *lenp : (DN_ASCBUF_LEN-1);
+
+		if (copy_from_user(addr, buffer, len))
+			return -EFAULT;
+
+		addr[len] = 0;
+		strip_it(addr);
+
+		if (parse_addr(&dnaddr, addr))
+			return -EINVAL;
+
+		dn_dev_devices_off();
+
+		decnet_address = dnaddr;
+
+		dn_dev_devices_on();
+
+		*ppos += len;
+
+		return 0;
+	}
+
+	dn_addr2asc(dn_ntohs(decnet_address), addr);
+	len = strlen(addr);
+	addr[len++] = '\n';
+
+	if (len > *lenp) len = *lenp;
+
+	if (copy_to_user(buffer, addr, len))
+		return -EFAULT;
+
+	*lenp = len;
+	*ppos += len;
+
+	return 0;
+}
+
+
+static int dn_def_dev_strategy(ctl_table *table, int __user *name, int nlen,
+				void __user *oldval, size_t __user *oldlenp,
+				void __user *newval, size_t newlen,
+				void **context)
+{
+	size_t len;
+	struct net_device *dev;
+	char devname[17];
+	size_t namel;
+	int rv = 0;
+
+	devname[0] = 0;
+
+	if (oldval && oldlenp) {
+		if (get_user(len, oldlenp))
+			return -EFAULT;
+		if (len) {
+			dev = dn_dev_get_default();
+			if (dev) {
+				strcpy(devname, dev->name);
+				dev_put(dev);
+			}
+
+			namel = strlen(devname) + 1;
+			if (len > namel) len = namel;	
+
+			if (copy_to_user(oldval, devname, len))
+				return -EFAULT;
+
+			if (put_user(len, oldlenp))
+				return -EFAULT;
+		}
+	}
+
+	if (newval && newlen) {
+		if (newlen > 16)
+			return -E2BIG;
+
+		if (copy_from_user(devname, newval, newlen))
+			return -EFAULT;
+
+		devname[newlen] = 0;
+
+		dev = dev_get_by_name(devname);
+		if (dev == NULL)
+			return -ENODEV;
+
+		rv = -ENODEV;
+		if (dev->dn_ptr != NULL) {
+			rv = dn_dev_set_default(dev, 1);
+			if (rv)
+				dev_put(dev);
+		}
+	}
+
+	return rv;
+}
+
+
+static int dn_def_dev_handler(ctl_table *table, int write, 
+				struct file * filp,
+				void __user *buffer,
+				size_t *lenp, loff_t *ppos)
+{
+	size_t len;
+	struct net_device *dev;
+	char devname[17];
+
+	if (!*lenp || (*ppos && !write)) {
+		*lenp = 0;
+		return 0;
+	}
+
+	if (write) {
+		if (*lenp > 16)
+			return -E2BIG;
+
+		if (copy_from_user(devname, buffer, *lenp))
+			return -EFAULT;
+
+		devname[*lenp] = 0;
+		strip_it(devname);
+
+		dev = dev_get_by_name(devname);
+		if (dev == NULL)
+			return -ENODEV;
+
+		if (dev->dn_ptr == NULL) {
+			dev_put(dev);
+			return -ENODEV;
+		}
+
+		if (dn_dev_set_default(dev, 1)) {
+			dev_put(dev);
+			return -ENODEV;
+		}
+		*ppos += *lenp;
+
+		return 0;
+	}
+
+	dev = dn_dev_get_default();
+	if (dev == NULL) {
+		*lenp = 0;
+		return 0;
+	}
+
+	strcpy(devname, dev->name);
+	dev_put(dev);
+	len = strlen(devname);
+	devname[len++] = '\n';
+
+	if (len > *lenp) len = *lenp;
+
+	if (copy_to_user(buffer, devname, len))
+		return -EFAULT;
+
+	*lenp = len;
+	*ppos += len;
+
+	return 0;
+}
+
+static ctl_table dn_table[] = {
+	{
+		.ctl_name = NET_DECNET_NODE_ADDRESS, 
+		.procname = "node_address", 
+		.maxlen = 7, 
+		.mode = 0644, 
+		.proc_handler = dn_node_address_handler,
+		.strategy = dn_node_address_strategy,
+	},
+	{
+		.ctl_name = NET_DECNET_NODE_NAME,
+		.procname = "node_name",
+		.data = node_name, 
+		.maxlen = 7,
+		.mode = 0644,
+		.proc_handler = &proc_dostring,
+		.strategy = &sysctl_string,
+	},
+	{
+		.ctl_name = NET_DECNET_DEFAULT_DEVICE,
+		.procname = "default_device", 
+		.maxlen = 16, 
+		.mode = 0644,
+		.proc_handler = dn_def_dev_handler,
+		.strategy = dn_def_dev_strategy,
+	},
+	{
+		.ctl_name = NET_DECNET_TIME_WAIT,
+		.procname = "time_wait",
+		.data = &decnet_time_wait,
+		.maxlen = sizeof(int),
+		.mode = 0644,
+		.proc_handler = &proc_dointvec_minmax,
+		.strategy = &sysctl_intvec,
+		.extra1 = &min_decnet_time_wait,
+		.extra2 = &max_decnet_time_wait
+	},
+	{
+		.ctl_name = NET_DECNET_DN_COUNT,
+		.procname = "dn_count",
+		.data = &decnet_dn_count,
+		.maxlen = sizeof(int),
+		.mode = 0644,
+		.proc_handler = &proc_dointvec_minmax,
+		.strategy = &sysctl_intvec,
+		.extra1 = &min_state_count,
+		.extra2 = &max_state_count
+	},
+	{
+		.ctl_name = NET_DECNET_DI_COUNT,
+		.procname = "di_count",
+		.data = &decnet_di_count,
+		.maxlen = sizeof(int),
+		.mode = 0644,
+		.proc_handler = &proc_dointvec_minmax,
+		.strategy = &sysctl_intvec,
+		.extra1 = &min_state_count,
+		.extra2 = &max_state_count
+	},
+	{
+		.ctl_name = NET_DECNET_DR_COUNT,
+		.procname = "dr_count",
+		.data = &decnet_dr_count,
+		.maxlen = sizeof(int),
+		.mode = 0644,
+		.proc_handler = &proc_dointvec_minmax,
+		.strategy = &sysctl_intvec,
+		.extra1 = &min_state_count,
+		.extra2 = &max_state_count
+	},
+	{
+		.ctl_name = NET_DECNET_DST_GC_INTERVAL,
+		.procname = "dst_gc_interval",
+		.data = &decnet_dst_gc_interval,
+		.maxlen = sizeof(int),
+		.mode = 0644,
+		.proc_handler = &proc_dointvec_minmax,
+		.strategy = &sysctl_intvec,
+		.extra1 = &min_decnet_dst_gc_interval,
+		.extra2 = &max_decnet_dst_gc_interval
+	},
+	{
+		.ctl_name = NET_DECNET_NO_FC_MAX_CWND,
+		.procname = "no_fc_max_cwnd",
+		.data = &decnet_no_fc_max_cwnd,
+		.maxlen = sizeof(int),
+		.mode = 0644,
+		.proc_handler = &proc_dointvec_minmax,
+		.strategy = &sysctl_intvec,
+		.extra1 = &min_decnet_no_fc_max_cwnd,
+		.extra2 = &max_decnet_no_fc_max_cwnd
+	},
+	{
+		.ctl_name = NET_DECNET_DEBUG_LEVEL,
+		.procname = "debug",
+		.data = &decnet_debug_level,
+		.maxlen = sizeof(int),
+		.mode = 0644,
+		.proc_handler = &proc_dointvec,
+		.strategy = &sysctl_intvec,
+	},
+	{0}
+};
+
+static ctl_table dn_dir_table[] = {
+	{
+		.ctl_name = NET_DECNET, 
+		.procname = "decnet", 
+		.mode = 0555, 
+		.child = dn_table},
+	{0}
+};
+
+static ctl_table dn_root_table[] = {
+	{
+		.ctl_name = CTL_NET, 
+		.procname = "net", 
+		.mode = 0555, 
+		.child = dn_dir_table
+	},
+	{0}
+};
+
+void dn_register_sysctl(void)
+{
+	dn_table_header = register_sysctl_table(dn_root_table, 1);
+}
+
+void dn_unregister_sysctl(void)
+{
+	unregister_sysctl_table(dn_table_header);
+}
+
+#else  /* CONFIG_SYSCTL */
+void dn_unregister_sysctl(void)
+{
+}
+void dn_register_sysctl(void)
+{
+}
+
+#endif