summaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
Diffstat (limited to 'net')
-rw-r--r--net/decnet/Config.in13
-rw-r--r--net/decnet/Makefile30
-rw-r--r--net/decnet/TODO59
-rw-r--r--net/decnet/af_decnet.c2192
-rw-r--r--net/decnet/dn_dev.c1386
-rw-r--r--net/decnet/dn_fib.c805
-rw-r--r--net/decnet/dn_neigh.c633
-rw-r--r--net/decnet/dn_nsp_in.c703
-rw-r--r--net/decnet/dn_nsp_out.c640
-rw-r--r--net/decnet/dn_raw.c383
-rw-r--r--net/decnet/dn_route.c1028
-rw-r--r--net/decnet/dn_timer.c164
-rw-r--r--net/decnet/sysctl_net_decnet.c473
13 files changed, 8509 insertions, 0 deletions
diff --git a/net/decnet/Config.in b/net/decnet/Config.in
new file mode 100644
index 000000000..ac12d2aca
--- /dev/null
+++ b/net/decnet/Config.in
@@ -0,0 +1,13 @@
+#
+# DECnet configuration
+#
+bool 'DECnet: SIOCGIFCONF support' CONFIG_DECNET_SIOCGIFCONF
+bool 'DECnet: router support (VERY VERY EXPERIMENTAL)' CONFIG_DECNET_ROUTER
+bool 'DECnet: raw socket support' CONFIG_DECNET_RAW
+#bool 'DECnet: MOP support' CONFIG_DECNET_MOP
+#if [ "$CONFIG_FIREWALL" = "y" ]; then
+# bool 'DECnet: firewall support' CONFIG_DECNET_FW
+# if [ "$CONFIG_DECNET_FW" = "y" ]; then
+# bool 'DECnet: firewall netlink support' CONFIG_DECNET_FIREWALL_NETLINK
+# fi
+#fi
diff --git a/net/decnet/Makefile b/net/decnet/Makefile
new file mode 100644
index 000000000..d99da95a1
--- /dev/null
+++ b/net/decnet/Makefile
@@ -0,0 +1,30 @@
+# Note 2! The CFLAGS definition is now in the main makefile...
+
+O_TARGET := decnet.o
+O_OBJS := af_decnet.o dn_nsp_in.o dn_nsp_out.o dn_route.o dn_dev.o dn_neigh.o dn_timer.o
+M_OBJS := $(O_TARGET)
+
+ifeq ($(CONFIG_DECNET_ROUTER),y)
+O_OBJS += dn_fib.o
+endif
+
+ifeq ($(CONFIG_DECNET_RAW),y)
+O_OBJS += dn_raw.o
+endif
+
+#ifeq ($(CONFIG_DECNET_MOP),y)
+#O_OBJS += dn_mop.o
+#endif
+
+ifeq ($(CONFIG_DECNET_FW),y)
+O_OBJS += dn_fw.o
+endif
+
+ifeq ($(CONFIG_SYSCTL),y)
+O_OBJS += sysctl_net_decnet.o
+endif
+
+include $(TOPDIR)/Rules.make
+
+tar:
+ tar -cvf /dev/f1 .
diff --git a/net/decnet/TODO b/net/decnet/TODO
new file mode 100644
index 000000000..48ea5e212
--- /dev/null
+++ b/net/decnet/TODO
@@ -0,0 +1,59 @@
+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 MOP support (probably as part of Raw sockets) [hooks in]
+
+ o Routing stuff in dn_fib.c
+
+ o Misc. get/set_sockopt() functions [done for the time being, more later]
+
+ o Support for X.25 linklayer
+
+ o Support for DDCMP link layer
+
+ o The DDCMP device itself
+
+ o PPP support (rfc1762)
+
+ o sendmsg() in the raw socket layer
+
+ o Better filtering of traffic in raw sockets
+
+ o Fix /proc for raw sockets
+
+ 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 recvmsg() to optionally report remote address.
+
+ o check MSG_TRUNC, MSG_CTRUNC are set where they should be.
+
+ o Work out if I really need support for rtnetlink "link" messages and if
+ so how they should be handled.
+
+ o More rtnetlink "route" message support & testing of this code
+
+ o Routing ioctl() support
+
+ o Start to hack together user level software and add more DECnet support
+ in ifconfig for example. Also a DECnet equivalent to Alexey's ip config
+ tool is required. Hopefully I can steal some code from that.
+
+ o Sort out MSG_EOR in sendmsg.... should it be used, or is each transmission
+ a seperate message ? What about when you get interrupted by a signal ?
+
+ o Fix conninit_rx to check out each CI before queuing it
+
+ o Work out which errors we can return from conninit_rx, and how to do it
+
+ o Check out receiving of errors in the light of what conninit_rx can return
+
diff --git a/net/decnet/af_decnet.c b/net/decnet/af_decnet.c
new file mode 100644
index 000000000..9d355f752
--- /dev/null
+++ b/net/decnet/af_decnet.c
@@ -0,0 +1,2192 @@
+
+/*
+ * 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.
+ */
+
+
+/******************************************************************************
+ (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/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/segment.h>
+#include <asm/system.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_raw.h>
+#include <net/dn_neigh.h>
+
+#define MAX(a,b) ((a)>(b)?(a):(b))
+
+static void dn_keepalive(struct sock *sk);
+
+/*
+ * decnet_address is kept in network order, decnet_ether_address is kept
+ * as a string of bytes.
+ */
+dn_address decnet_address = 0;
+unsigned char decnet_ether_address[ETH_ALEN] = { 0xAA, 0x00, 0x04, 0x00, 0x00, 0x00 };
+int decnet_node_type = DN_RT_INFO_ENDN;
+
+static struct proto_ops dn_proto_ops;
+static struct sock *dn_sklist = NULL;
+static struct sock *dn_wild_sk = NULL;
+
+static int _dn_setsockopt(struct socket *sock, int level, int optname, char *optval, int optlen, int flags);
+static int _dn_getsockopt(struct socket *sock, int level, int optname, char *optval, int *optlen, int flags);
+
+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++ = sdn->sdn_objnamel;
+ memcpy(buf, sdn->sdn_objname, sdn->sdn_objnamel);
+ len = 3 + sdn->sdn_objnamel;
+ break;
+ case 2:
+ memset(buf, 0, 5);
+ buf += 5;
+ *buf++ = sdn->sdn_objnamel;
+ memcpy(buf, sdn->sdn_objname, sdn->sdn_objnamel);
+ len = 7 + 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 = 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 = *data++;
+ len -= sdn->sdn_objnamel;
+
+ if ((len < 0) || (sdn->sdn_objnamel > namel))
+ return -1;
+
+ memcpy(sdn->sdn_objname, data, sdn->sdn_objnamel);
+
+ return size - len;
+}
+
+struct sock *dn_sklist_find_listener(struct sockaddr_dn *addr)
+{
+ struct sock *sk;
+
+ for(sk = dn_sklist; sk != NULL; sk = sk->next) {
+ struct dn_scp *scp = &sk->protinfo.dn;
+ if (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, addr->sdn_objnamel) != 0)
+ continue;
+ }
+ return sk;
+ }
+
+ return (dn_wild_sk && (dn_wild_sk->state == TCP_LISTEN)) ? dn_wild_sk : NULL;
+}
+
+struct sock *dn_sklist_find(unsigned short port)
+{
+ struct sock *s;
+
+ for (s = dn_sklist; s != NULL; s = s->next) {
+ if (s->protinfo.dn.addrloc == port) {
+ return s;
+ }
+ }
+
+ return NULL;
+}
+
+static struct sock *dn_sklist_find_by_objnum(unsigned char objnum)
+{
+ struct sock *s;
+
+ for (s = dn_sklist; s != NULL; s = s->next) {
+ if ((s->protinfo.dn.addr.sdn_objnum == objnum) &&
+ (s->state == TCP_LISTEN)) {
+ return s;
+ }
+ }
+ return NULL;
+}
+
+static struct sock *dn_sklist_find_by_name(char *name)
+{
+ struct sock *s;
+
+ for (s = dn_sklist; s != NULL; s = s->next) {
+ if (s->protinfo.dn.addr.sdn_objnum)
+ continue;
+ if ((memcmp(s->protinfo.dn.addr.sdn_objname,name,
+ s->protinfo.dn.addr.sdn_objnamel) == 0)
+ && (s->state == TCP_LISTEN)) {
+ return s;
+ }
+ }
+ return NULL;
+}
+
+
+struct sock *dn_find_by_skb(struct sk_buff *skb)
+{
+ struct dn_skb_cb *cb = (struct dn_skb_cb *)skb->cb;
+ struct sock *sk;
+ struct dn_scp *scp;
+
+ for(sk = dn_sklist; sk != NULL; sk = sk->next) {
+ scp = &sk->protinfo.dn;
+ if (cb->src != dn_saddr2dn(&scp->peer))
+ continue;
+ if (cb->dst_port != scp->addrloc)
+ continue;
+ if (scp->addrrem && (cb->src_port != scp->addrrem))
+ continue;
+ break;
+ }
+
+ return sk;
+}
+
+
+unsigned short dn_alloc_port(void)
+{
+ struct sock *sk;
+ static unsigned short dn_port = 0x2000;
+ short port;
+
+ start_bh_atomic();
+
+ do {
+ port = dn_port++;
+ sk = dn_sklist_find(port);
+ } while((sk != NULL) || (port == 0));
+
+ end_bh_atomic();
+
+ return dn_htons(port);
+};
+
+
+static void dn_destruct(struct sock *sk)
+{
+ struct dn_scp *scp = &sk->protinfo.dn;
+
+ 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->dst_cache, NULL));
+
+ MOD_DEC_USE_COUNT;
+}
+
+struct sock *dn_alloc_sock(struct socket *sock, int flags)
+{
+ struct sock *sk;
+ struct dn_scp *scp;
+
+ if ((sk = sk_alloc(PF_DECnet, flags, 1)) == NULL)
+ goto no_sock;
+
+ if (sock) {
+#ifdef CONFIG_DECNET_RAW
+ if (sock->type == SOCK_RAW)
+ sock->ops = &dn_raw_proto_ops;
+ else
+#endif /* CONFIG_DECNET_RAW */
+ sock->ops = &dn_proto_ops;
+ }
+ sock_init_data(sock,sk);
+ scp = &sk->protinfo.dn;
+
+ sk->backlog_rcv = dn_nsp_backlog_rcv;
+ sk->destruct = dn_destruct;
+ sk->no_check = 1;
+ sk->family = PF_DECnet;
+ sk->protocol = 0;
+
+ /* Initialization of DECnet Session Control Port */
+ 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->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->mss = 1460;
+
+ 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);
+
+ MOD_INC_USE_COUNT;
+
+ return sk;
+no_sock:
+ return NULL;
+}
+
+/*
+ * Keepalive timer.
+ * FIXME: Should respond to SO_KEEPALIVE etc.
+ */
+static void dn_keepalive(struct sock *sk)
+{
+ struct dn_scp *scp = &sk->protinfo.dn;
+
+ /*
+ * 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_lnk(sk, DN_NOCHANGE);
+}
+
+
+/*
+ * 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. This is called by both
+ * socket level and interrupt driven code.
+ */
+static int dn_destroy_timer(struct sock *sk)
+{
+ struct dn_scp *scp = &sk->protinfo.dn;
+
+ scp->persist = dn_nsp_persist(sk);
+
+ switch(scp->state) {
+ case DN_DI:
+ /* printk(KERN_DEBUG "dn_destroy_timer: DI\n"); */
+ dn_send_disc(sk, NSP_DISCINIT, 0);
+ if (scp->nsp_rxtshift >= decnet_di_count)
+ scp->state = DN_CN;
+ return 0;
+
+ case DN_DR:
+ /* printk(KERN_DEBUG "dn_destroy_timer: DR\n"); */
+ dn_send_disc(sk, NSP_DISCINIT, 0);
+ 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_send_disc(sk, NSP_DISCCONF, NSP_REASON_DC);
+ return 0;
+ }
+ }
+
+ scp->persist = (HZ * decnet_time_wait);
+
+/* printk(KERN_DEBUG "dn_destroy_timer: testing dead\n"); */
+
+ if (sk->socket)
+ return 0;
+
+ dn_stop_fast_timer(sk); /* unlikely, but possible that this is runninng */
+ if ((jiffies - scp->stamp) >= (HZ * decnet_time_wait)) {
+ sklist_destroy_socket(&dn_sklist, sk);
+ return 1;
+ }
+
+ /*printk(KERN_DEBUG "dn_destroy_timer: dead 'n' waiting...\n"); */
+
+ return 0;
+}
+
+void dn_destroy_sock(struct sock *sk)
+{
+ struct dn_scp *scp = &sk->protinfo.dn;
+
+ if (sk->dead)
+ return;
+
+ sk->dead = 1;
+ scp->nsp_rxtshift = 0; /* reset back off */
+
+ if (sk->socket) {
+ if (sk->socket->state != SS_UNCONNECTED)
+ sk->socket->state = SS_DISCONNECTING;
+ }
+
+ sk->state = TCP_CLOSE;
+
+ switch(scp->state) {
+ case DN_DN:
+ dn_send_disc(sk, NSP_DISCCONF, NSP_REASON_DC);
+ scp->persist_fxn = dn_destroy_timer;
+ scp->persist = dn_nsp_persist(sk);
+ break;
+ case DN_CD:
+ 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_send_disc(sk, NSP_DISCINIT, 0);
+ case DN_NC:
+ case DN_NR:
+ case DN_RJ:
+ case DN_DIC:
+ case DN_CN:
+ case DN_DRC:
+ case DN_CI:
+ 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:
+ start_bh_atomic();
+ dn_stop_fast_timer(sk);
+ dn_stop_slow_timer(sk);
+
+ if (sk == dn_wild_sk) {
+ dn_wild_sk = NULL;
+ sklist_destroy_socket(NULL, sk);
+ } else {
+ sklist_destroy_socket(&dn_sklist, sk);
+ }
+
+ end_bh_atomic();
+ 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 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 int dn_get_info(char *buffer, char **start, off_t offset,
+ int length, int dummy)
+{
+ struct sock *sk;
+ int len = 0;
+ off_t pos = 0;
+ off_t begin = 0;
+ char buf[DN_ASCBUF_LEN];
+
+ len += sprintf(buffer+len,"%-8s%-7s%-7s%-7s%-5s%-13s%-13s\n",
+ "Remote","Source","Remote","Object","Link ",
+ " Data Packets ","Link Packets");
+ len += sprintf(buffer+len,"%-8s%-7s%-7s%-7s%-5s%-13s%-13s\n\n",
+ "Node ","Port ","Port ","Number","State",
+ " Out In "," Out In");
+ start_bh_atomic();
+ for (sk = dn_sklist; sk != NULL; sk = sk->next) {
+ len += sprintf(buffer+len,
+ "%6s %04X %04X %6d %4s %6d %6d %6d %6d\n",
+
+ dn_addr2asc(dn_ntohs(dn_saddr2dn(&sk->protinfo.dn.peer)), buf),
+ sk->protinfo.dn.addrloc,sk->protinfo.dn.addrrem,
+ sk->protinfo.dn.addr.sdn_objnum,
+ dn_state2asc(sk->protinfo.dn.state),
+ sk->protinfo.dn.numdat, sk->protinfo.dn.numdat_rcv,
+ sk->protinfo.dn.numoth, sk->protinfo.dn.numoth_rcv);
+
+ pos = begin + len;
+ if (pos < offset) {
+ len = 0;
+ begin = pos;
+ }
+ if (pos > offset+length)
+ break;
+ }
+ end_bh_atomic();
+
+ *start = buffer + (offset - begin);
+ len -= (offset - begin);
+
+ if (len > length)
+ len = length;
+
+ return len;
+}
+
+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;
+#ifdef CONFIG_DECNET_RAW
+ case SOCK_RAW:
+ if ((protocol != DNPROTO_NSP) &&
+#ifdef CONFIG_DECNET_MOP
+ (protocol != DNPROTO_MOP) &&
+#endif /* CONFIG_DECNET_MOP */
+ (protocol != DNPROTO_ROU))
+ return -EPROTONOSUPPORT;
+ break;
+#endif /* CONFIG_DECNET_RAW */
+ default:
+ return -ESOCKTNOSUPPORT;
+ }
+
+
+ if ((sk = dn_alloc_sock(sock, GFP_KERNEL)) == NULL)
+ return -ENOBUFS;
+
+ sk->protocol = protocol;
+
+ return 0;
+}
+
+
+static int
+dn_release(struct socket *sock, struct socket *peer)
+{
+ struct sock *sk = sock->sk;
+
+ if (sk) {
+ lock_sock(sk);
+ sock->sk = NULL;
+ sk->socket = NULL;
+ dn_destroy_sock(sk);
+ release_sock(sk);
+ }
+
+ return 0;
+}
+
+static int dn_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_dn *saddr = (struct sockaddr_dn *)uaddr;
+
+ if (sk->zapped == 0)
+ return -EINVAL;
+
+ if (addr_len != sizeof(struct sockaddr_dn))
+ return -EINVAL;
+
+ if (saddr->sdn_family != AF_DECnet)
+ return -EINVAL;
+
+ if (saddr->sdn_objnum && !suser())
+ return -EPERM;
+
+ if (!saddr->sdn_objname && (saddr->sdn_objnamel > DN_MAXOBJL))
+ return -EINVAL;
+
+ if (saddr->sdn_flags & ~SDF_WILD)
+ return -EINVAL;
+
+ if ((saddr->sdn_flags & SDF_WILD) && !suser())
+ return -EPERM;
+
+ start_bh_atomic();
+
+ if (saddr->sdn_flags & SDF_WILD) {
+ if (dn_wild_sk) {
+ end_bh_atomic();
+ return -EADDRINUSE;
+ }
+ dn_wild_sk = sk;
+ sk->zapped = 0;
+ memcpy(&sk->protinfo.dn.addr, saddr, addr_len);
+ end_bh_atomic();
+ return 0;
+ }
+
+ if (saddr->sdn_objnum && dn_sklist_find_by_objnum(saddr->sdn_objnum)) {
+ end_bh_atomic();
+ return -EADDRINUSE;
+ }
+
+ if (!saddr->sdn_objnum) {
+ if (dn_sklist_find_by_name(saddr->sdn_objname)) {
+ end_bh_atomic();
+ return -EADDRINUSE;
+ }
+ }
+
+ memcpy(&sk->protinfo.dn.addr, saddr, addr_len);
+ sk->zapped = 0;
+ sklist_insert_socket(&dn_sklist, sk);
+ end_bh_atomic();
+
+ return 0;
+}
+
+
+static int dn_auto_bind(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+ struct dn_scp *scp = &sk->protinfo.dn;
+
+ sk->zapped = 0;
+
+ 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 = scp->accessdata.acc_accl;
+ memcpy(scp->addr.sdn_objname, scp->accessdata.acc_acc, scp->addr.sdn_objnamel);
+
+ scp->accessdata.acc_accl = 0;
+ memset(scp->accessdata.acc_acc, 0, 40);
+ }
+
+ scp->addr.sdn_add.a_len = 2;
+ *(dn_address *)scp->addr.sdn_add.a_addr = decnet_address;
+
+ sklist_insert_socket(&dn_sklist, sk);
+
+ return 0;
+}
+
+
+static int dn_connect(struct socket *sock, struct sockaddr *uaddr, int addr_len, int flags)
+{
+ struct sockaddr_dn *addr = (struct sockaddr_dn *)uaddr;
+ struct sock *sk = sock->sk;
+ int err = -EISCONN;
+
+ lock_sock(sk);
+
+ if (sock->state == SS_CONNECTED)
+ goto out;
+
+ if (sock->state == SS_CONNECTING) {
+ err = 0;
+ if (sk->state == TCP_ESTABLISHED)
+ goto out;
+
+ err = -ECONNREFUSED;
+ if (sk->state == TCP_CLOSE)
+ goto out;
+ }
+
+ err = -EINVAL;
+ if (sk->protinfo.dn.state != DN_O)
+ goto out;
+
+ if (addr_len != sizeof(struct sockaddr_dn))
+ goto out;
+
+ if (addr->sdn_family != AF_DECnet)
+ goto out;
+
+ if (addr->sdn_flags & SDF_WILD)
+ goto out;
+
+ err = -EADDRNOTAVAIL;
+ if (sk->zapped && (err = dn_auto_bind(sock)))
+ goto out;
+
+ memcpy(&sk->protinfo.dn.peer, addr, addr_len);
+
+ err = -EHOSTUNREACH;
+ if (dn_route_output(sk) < 0)
+ goto out;
+
+ sk->state = TCP_SYN_SENT;
+ sock->state = SS_CONNECTING;
+ sk->protinfo.dn.state = DN_CI;
+
+ dn_nsp_send_conninit(sk, NSP_CI);
+
+ err = -EINPROGRESS;
+ if ((sk->state == TCP_SYN_SENT) && (flags & O_NONBLOCK))
+ goto out;
+
+ while(sk->state == TCP_SYN_SENT) {
+
+ err = -ERESTARTSYS;
+ if (signal_pending(current))
+ goto out;
+
+ if ((err = sock_error(sk)) != 0) {
+ sock->state = SS_UNCONNECTED;
+ goto out;
+ }
+
+ SOCK_SLEEP_PRE(sk);
+
+ if (sk->state == TCP_SYN_SENT)
+ schedule();
+
+ SOCK_SLEEP_POST(sk);
+ }
+
+ if (sk->state != TCP_ESTABLISHED) {
+ sock->state = SS_UNCONNECTED;
+ err = sock_error(sk);
+ goto out;
+ }
+
+ err = 0;
+ sock->state = SS_CONNECTED;
+out:
+ release_sock(sk);
+
+ return err;
+}
+
+static int 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);
+
+ return 0;
+}
+
+static int 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);
+
+ return 0;
+}
+
+
+/*
+ * This is here for use in the sockopt() call as well as
+ * in accept(). Must be called with a locked socket.
+ */
+static int dn_wait_accept(struct socket *sock, int flags)
+{
+ struct sock *sk = sock->sk;
+
+ /* printk(KERN_DEBUG "dn_wait_accept: in\n"); */
+
+ while(sk->state == TCP_LISTEN) {
+ if (flags & O_NONBLOCK) {
+ return -EAGAIN;
+ }
+
+ SOCK_SLEEP_PRE(sk)
+
+ if (sk->state == TCP_LISTEN)
+ schedule();
+
+ SOCK_SLEEP_POST(sk)
+
+ if (signal_pending(current)) {
+ /* printk(KERN_DEBUG "dn_wait_accept: signal\n"); */
+ return -ERESTARTSYS; /* But of course you don't! */
+ }
+ }
+
+ if ((sk->protinfo.dn.state != DN_RUN) && (sk->protinfo.dn.state != DN_DRC)) {
+ sock->state = SS_UNCONNECTED;
+ return sock_error(sk);
+ }
+
+ sock->state = SS_CONNECTED;
+ /* printk(KERN_DEBUG "dn_wait_accept: out\n"); */
+
+ return 0;
+}
+
+
+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;
+
+ lock_sock(sk);
+
+ if (sk->state != TCP_LISTEN) {
+ release_sock(sk);
+ return -EINVAL;
+ }
+
+ if (sk->protinfo.dn.state != DN_O) {
+ release_sock(sk);
+ return -EINVAL;
+ }
+
+ if (newsock->sk != NULL) {
+ newsock->sk->socket = NULL;
+ dn_destroy_sock(newsock->sk);
+ newsock->sk = NULL;
+ }
+
+ do
+ {
+ /* printk(KERN_DEBUG "dn_accept: loop top\n"); */
+ if ((skb = skb_dequeue(&sk->receive_queue)) == NULL)
+ {
+ if (flags & O_NONBLOCK)
+ {
+ release_sock(sk);
+ return -EAGAIN;
+ }
+
+ SOCK_SLEEP_PRE(sk);
+
+ if (!skb_peek(&sk->receive_queue))
+ schedule();
+
+ SOCK_SLEEP_POST(sk);
+
+ if (signal_pending(current))
+ {
+ release_sock(sk);
+ return -ERESTARTSYS;
+ }
+ }
+ } while (skb == NULL);
+
+ cb = (struct dn_skb_cb *)skb->cb;
+
+ if ((newsk = dn_alloc_sock(newsock, GFP_KERNEL)) == NULL) {
+ release_sock(sk);
+ kfree_skb(skb);
+ return -ENOBUFS;
+ }
+ sk->ack_backlog--;
+ release_sock(sk);
+
+ dst_release(xchg(&newsk->dst_cache, skb->dst));
+ skb->dst = NULL;
+
+
+ newsk->protinfo.dn.state = DN_CR;
+ newsk->protinfo.dn.addrloc = dn_alloc_port();
+ newsk->protinfo.dn.addrrem = cb->src_port;
+ newsk->protinfo.dn.mss = cb->segsize;
+ newsk->protinfo.dn.accept_mode = sk->protinfo.dn.accept_mode;
+
+ if (newsk->protinfo.dn.mss < 230)
+ newsk->protinfo.dn.mss = 230;
+
+ newsk->state = TCP_LISTEN;
+ newsk->zapped = 0;
+
+ memcpy(&newsk->protinfo.dn.addr, &sk->protinfo.dn.addr, sizeof(struct sockaddr_dn));
+
+ skb_pull(skb, dn_username2sockaddr(skb->data, skb->len, &newsk->protinfo.dn.addr, &type));
+ skb_pull(skb, dn_username2sockaddr(skb->data, skb->len, &newsk->protinfo.dn.peer, &type));
+ *(dn_address *)newsk->protinfo.dn.peer.sdn_add.a_addr = cb->src;
+
+ menuver = *skb->data;
+ skb_pull(skb, 1);
+
+ if (menuver & DN_MENUVER_ACC)
+ dn_access_copy(skb, &newsk->protinfo.dn.accessdata);
+
+ if (menuver & DN_MENUVER_USR)
+ dn_user_copy(skb, &newsk->protinfo.dn.conndata_in);
+
+ if (menuver & DN_MENUVER_PRX)
+ newsk->protinfo.dn.peer.sdn_flags |= SDF_PROXY;
+
+ if (menuver & DN_MENUVER_UIC)
+ newsk->protinfo.dn.peer.sdn_flags |= SDF_UICPROXY;
+
+ kfree_skb(skb);
+
+ memcpy(&newsk->protinfo.dn.conndata_out, &sk->protinfo.dn.conndata_out,
+ sizeof(struct optdata_dn));
+ memcpy(&newsk->protinfo.dn.discdata_out, &sk->protinfo.dn.discdata_out,
+ sizeof(struct optdata_dn));
+
+ lock_sock(newsk);
+ sklist_insert_socket(&dn_sklist, newsk);
+
+ dn_send_conn_ack(newsk);
+
+ if (newsk->protinfo.dn.accept_mode == ACC_IMMED) {
+ newsk->protinfo.dn.state = DN_CC;
+ dn_send_conn_conf(newsk);
+ err = dn_wait_accept(newsock, flags);
+ }
+
+ 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 = &sk->protinfo.dn;
+
+ *uaddr_len = sizeof(struct sockaddr_dn);
+
+ lock_sock(sk);
+
+ if (peer) {
+ if (sock->state != SS_CONNECTED && sk->protinfo.dn.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 = &sk->protinfo.dn;
+ 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;
+ int err = -EOPNOTSUPP;
+ unsigned long amount = 0;
+ struct sk_buff *skb;
+
+#if 0
+ struct dn_naddr dnaddr;
+#endif
+ switch(cmd)
+ {
+ case SIOCGIFADDR:
+ case SIOCSIFADDR:
+ return dn_dev_ioctl(cmd, (void *)arg);
+
+#ifdef CONFIG_DECNET_ROUTER
+ case SIOCADDRT:
+ case SIOCDELRT:
+ return dn_fib_ioctl(sock, cmd, arg);
+#endif /* CONFIG_DECNET_ROUTER */
+
+#if 0
+ case SIOCSIFADDR:
+ if (!suser()) return -EPERM;
+
+ if ((err = copy_from_user(devname, ioarg->devname, 5)) != 0)
+ break;
+ if ((err = copy_from_user(addr, ioarg->exec_addr, 6)) != 0)
+ break;
+ if ((dev = dev_get(devname)) == NULL) {
+ err = -ENODEV;
+ break;
+ }
+ if (dev->dn_ptr == NULL) {
+ err = -ENODEV;
+ break;
+ }
+
+ dn_dev_devices_off();
+
+ decnet_default_device = dev;
+ memcpy(decnet_ether_address, addr, ETH_ALEN);
+ decnet_address = dn_htons(dn_eth2dn(decnet_ether_address));
+
+ dn_dev_devices_on();
+
+ break;
+
+ case SIOCGIFADDR:
+ if (decnet_default_device)
+ strcpy(devname, decnet_default_device->name);
+ else
+ memset(devname, 0, 6);
+
+ if ((err = copy_to_user(ioarg->devname, devname, 5)) != 0)
+ break;
+
+ if ((err = copy_to_user(ioarg->exec_addr, decnet_ether_address, 6)) != 0)
+ break;
+
+ break;
+#endif
+
+#if 0
+ case SIOCSNETADDR:
+ if (!suser()) {
+ err = -EPERM;
+ break;
+ }
+
+ if ((err = copy_from_user(&dnaddr, (void *)arg, sizeof(struct dn_naddr))) != 0)
+ break;
+
+ if (dnaddr.a_len != ETH_ALEN) {
+ err = -EINVAL;
+ break;
+ }
+
+ dn_dev_devices_off();
+
+ memcpy(decnet_ether_address, dnaddr.a_addr, ETH_ALEN);
+ decnet_address = dn_htons(dn_eth2dn(decnet_ether_address));
+
+ dn_dev_devices_on();
+ break;
+
+ case SIOCGNETADDR:
+ dnaddr.a_len = ETH_ALEN;
+ memcpy(dnaddr.a_addr, decnet_ether_address, ETH_ALEN);
+
+ if ((err = copy_to_user((void *)arg, &dnaddr, sizeof(struct dn_naddr))) != 0)
+ break;
+
+ break;
+#endif
+ case OSIOCSNETADDR:
+ if (!suser()) {
+ err = -EPERM;
+ break;
+ }
+
+ dn_dev_devices_off();
+
+ decnet_address = (unsigned short)arg;
+ dn_dn2eth(decnet_ether_address, dn_ntohs(decnet_address));
+
+ dn_dev_devices_on();
+ err = 0;
+ break;
+
+ case OSIOCGNETADDR:
+ if ((err = put_user(decnet_address, (unsigned short *)arg)) != 0)
+ break;
+ break;
+ case SIOCGIFCONF:
+ case SIOCGIFFLAGS:
+ case SIOCGIFBRDADDR:
+ return dev_ioctl(cmd,(void *)arg);
+
+ case TIOCOUTQ:
+ amount = sk->sndbuf - atomic_read(&sk->wmem_alloc);
+ if (amount < 0)
+ amount = 0;
+ err = put_user(amount, (int *)arg);
+ break;
+
+ case TIOCINQ:
+ lock_sock(sk);
+ if ((skb = skb_peek(&sk->receive_queue)) != NULL)
+ amount = skb->len;
+ release_sock(sk);
+ err = put_user(amount, (int *)arg);
+ break;
+ }
+
+ return err;
+}
+
+static int dn_listen(struct socket *sock, int backlog)
+{
+ struct sock *sk = sock->sk;
+
+ if (sk->zapped)
+ return -EINVAL;
+
+ if ((sk->protinfo.dn.state != DN_O) || (sk->state == TCP_LISTEN))
+ return -EINVAL;
+
+ if (backlog > SOMAXCONN)
+ backlog = SOMAXCONN;
+
+ sk->max_ack_backlog = backlog;
+ sk->ack_backlog = 0;
+ sk->state = TCP_LISTEN;
+
+ return 0;
+}
+
+
+static int dn_shutdown(struct socket *sock, int how)
+{
+ struct sock *sk = sock->sk;
+ struct dn_scp *scp = &sk->protinfo.dn;
+ 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->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 *optval, int optlen)
+{
+ return _dn_setsockopt(sock, level, optname, optval, optlen, 0);
+}
+
+static int _dn_setsockopt(struct socket *sock, int level,int optname, char *optval, int optlen, int flags)
+{
+ struct sock *sk = sock->sk;
+ struct dn_scp *scp = &sk->protinfo.dn;
+ struct optdata_dn opt;
+ struct accessdata_dn acc;
+#ifdef CONFIG_DECNET_FW
+ char tmp_fw[MAX(sizeof(struct dn_fwtest),sizeof(struct dn_fwnew))];
+#endif
+ int err;
+
+ if (optlen && !optval)
+ return -EINVAL;
+
+ 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 (copy_from_user(&opt, optval, optlen))
+ return -EFAULT;
+
+ if (opt.opt_optl > 16)
+ return -EINVAL;
+
+ memcpy(&scp->conndata_out, &opt, sizeof(struct optdata_dn));
+ break;
+
+ case DSO_DISDATA:
+ if (sock->state != SS_CONNECTED && sk->protinfo.dn.accept_mode == ACC_IMMED)
+ return -ENOTCONN;
+
+ if (optlen != sizeof(struct optdata_dn))
+ return -EINVAL;
+
+ if (copy_from_user(&opt, optval, sizeof(struct optdata_dn)))
+ return -EFAULT;
+
+ if (opt.opt_optl > 16)
+ return -EINVAL;
+
+ memcpy(&scp->discdata_out, &opt, sizeof(struct optdata_dn));
+ 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 (copy_from_user(&acc, optval, sizeof(struct accessdata_dn)))
+ return -EFAULT;
+
+ if ((acc.acc_accl > DN_MAXACCL) ||
+ (acc.acc_passl > DN_MAXACCL) ||
+ (acc.acc_userl > DN_MAXACCL))
+ return -EINVAL;
+
+ memcpy(&scp->accessdata, &acc, sizeof(struct accessdata_dn));
+ break;
+
+ case DSO_ACCEPTMODE:
+ if (sock->state == SS_CONNECTED)
+ return -EISCONN;
+ if (scp->state != DN_O)
+ return -EINVAL;
+
+ if (optlen != sizeof(int))
+ return -EINVAL;
+
+ {
+ int mode;
+
+ if (get_user(mode, optval))
+ return -EFAULT;
+ if ((mode != ACC_IMMED) && (mode != ACC_DEFER))
+ return -EINVAL;
+
+ scp->accept_mode = (unsigned char)mode;
+ }
+ break;
+
+ case DSO_CONACCEPT:
+ lock_sock(sk);
+
+ if (scp->state != DN_CR)
+ return -EINVAL;
+
+ scp->state = DN_CC;
+ dn_send_conn_conf(sk);
+ err = dn_wait_accept(sock, sock->file->f_flags);
+ release_sock(sk);
+ return err;
+
+ case DSO_CONREJECT:
+ lock_sock(sk);
+
+ if (scp->state != DN_CR)
+ return -EINVAL;
+
+ scp->state = DN_DR;
+ sk->shutdown = SHUTDOWN_MASK;
+ dn_send_disc(sk, 0x38, 0);
+ release_sock(sk);
+ break;
+
+#ifdef CONFIG_DECNET_FW
+ case DN_FW_MASQ_TIMEOUTS:
+ case DN_FW_APPEND:
+ case DN_FW_REPLACE:
+ case DN_FW_DELETE:
+ case DN_FW_DELETE_NUM:
+ case DN_FW_INSERT:
+ case DN_FW_FLUSH:
+ case DN_FW_ZERO:
+ case DN_FW_CHECK:
+ case DN_FW_CREATECHAIN:
+ case DN_FW_DELETECHAIN:
+ case DN_FW_POLICY:
+
+ if (!capable(CAP_NET_ADMIN))
+ return -EACCES;
+ if ((optlen > sizeof(tmp_fw)) || (optlen < 1))
+ return -EINVAL;
+ if (copy_from_user(&tmp_fw, optval, optlen))
+ return -EFAULT;
+ err = dn_fw_ctl(optname, &tmp_fw, optlen);
+ return -err; /* -0 is 0 after all */
+#endif
+ default:
+ case DSO_LINKINFO:
+ case DSO_STREAM:
+ case DSO_SEQPACKET:
+
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int dn_getsockopt(struct socket *sock, int level, int optname, char *optval, int *optlen)
+{
+ return _dn_getsockopt(sock, level, optname, optval, optlen, 0);
+}
+
+static int
+_dn_getsockopt(struct socket *sock, int level,int optname, char *optval,int *optlen, int flags)
+{
+ struct sock *sk = sock->sk;
+ struct dn_scp *scp = &sk->protinfo.dn;
+ struct linkinfo_dn link;
+ int mode = scp->accept_mode;
+
+ switch(optname) {
+ case DSO_CONDATA:
+ if (*optlen != sizeof(struct optdata_dn))
+ return -EINVAL;
+
+ if (copy_to_user(optval, &scp->conndata_in, sizeof(struct optdata_dn)))
+ return -EFAULT;
+ break;
+
+ case DSO_DISDATA:
+ if (*optlen != sizeof(struct optdata_dn))
+ return -EINVAL;
+
+ if (copy_to_user(optval, &scp->discdata_in, sizeof(struct optdata_dn)))
+ return -EFAULT;
+
+ break;
+
+ case DSO_CONACCESS:
+ if (*optlen != sizeof(struct accessdata_dn))
+ return -EINVAL;
+
+ if (copy_to_user(optval, &scp->accessdata, sizeof(struct accessdata_dn)))
+ return -EFAULT;
+ break;
+
+ case DSO_ACCEPTMODE:
+ if (put_user(mode, optval))
+ return -EFAULT;
+ break;
+
+ case DSO_LINKINFO:
+ if (*optlen != sizeof(struct linkinfo_dn))
+ return -EINVAL;
+
+ 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->mss;
+
+ if (copy_to_user(optval, &link, sizeof(struct linkinfo_dn)))
+ return -EFAULT;
+ break;
+
+ case DSO_STREAM:
+ case DSO_SEQPACKET:
+ case DSO_CONACCEPT:
+ case DSO_CONREJECT:
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+
+/*
+ * Used by send/recvmsg to wait until the socket is connected
+ * before passing data.
+ */
+static int dn_wait_run(struct sock *sk, int flags)
+{
+ struct dn_scp *scp = &sk->protinfo.dn;
+ int err = 0;
+
+ /* printk(KERN_DEBUG "dn_wait_run %d\n", scp->state); */
+
+ switch(scp->state) {
+ case DN_RUN:
+ return 0;
+
+ case DN_CR:
+ scp->state = DN_CC;
+ dn_send_conn_conf(sk);
+ return dn_wait_accept(sk->socket, (flags & MSG_DONTWAIT) ? O_NONBLOCK : 0);
+ case DN_CI:
+ case DN_CC:
+ break;
+ default:
+ return -ENOTCONN;
+ goto out;
+ }
+
+ if (flags & MSG_DONTWAIT)
+ return -EWOULDBLOCK;
+
+ do {
+ if ((err = sock_error(sk)) != 0)
+ goto out;
+
+ if (signal_pending(current)) {
+ err = -ERESTARTSYS;
+ goto out;
+ }
+
+ SOCK_SLEEP_PRE(sk)
+
+ if (scp->state != DN_RUN)
+ schedule();
+
+ SOCK_SLEEP_POST(sk)
+
+ } while(scp->state != DN_RUN);
+
+out:
+
+ 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 = (struct dn_skb_cb *)skb->cb;
+ len += skb->len;
+
+ if (cb->nsp_flags & 0x40) {
+ /* SOCK_SEQPACKET reads to EOM */
+ if (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;
+ }
+
+ return 0;
+}
+
+
+static int dn_recvmsg(struct socket *sock, struct msghdr *msg, int size,
+ int flags, struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ struct dn_scp *scp = &sk->protinfo.dn;
+ struct sk_buff_head *queue = &sk->receive_queue;
+ int target = size > 1 ? 1 : 0;
+ int copied = 0;
+ int rv = 0;
+ struct sk_buff *skb = NULL, **pskb;
+ struct dn_skb_cb *cb = NULL;
+
+ lock_sock(sk);
+
+ if (sk->zapped) {
+ rv = -EADDRNOTAVAIL;
+ goto out;
+ }
+
+ if ((rv = dn_wait_run(sk, flags)) != 0)
+ goto out;
+
+ if (sk->shutdown & RCV_SHUTDOWN) {
+ send_sig(SIGPIPE, current, 0);
+ rv = -EPIPE;
+ goto out;
+ }
+
+ if (flags & ~(MSG_PEEK|MSG_OOB|MSG_WAITALL|MSG_DONTWAIT)) {
+ 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->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 = -ERESTARTSYS;
+ goto out;
+ }
+
+ if (dn_data_ready(sk, queue, flags, target))
+ break;
+
+ if (flags & MSG_DONTWAIT) {
+ rv = -EWOULDBLOCK;
+ goto out;
+ }
+
+ sock->flags |= SO_WAITDATA;
+ SOCK_SLEEP_PRE(sk)
+
+ if (!dn_data_ready(sk, queue, flags, target))
+ schedule();
+
+ SOCK_SLEEP_POST(sk)
+ sock->flags &= ~SO_WAITDATA;
+ }
+
+ pskb = &((struct sk_buff *)queue)->next;
+ while((skb = *pskb) != (struct sk_buff *)queue) {
+ int chunk = skb->len;
+ cb = (struct dn_skb_cb *)skb->cb;
+
+ 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->len -= chunk;
+
+ if (skb->len == 0) {
+ skb_unlink(skb);
+ kfree_skb(skb);
+ if ((scp->flowloc_sw == DN_DONTSEND) && !dn_congested(sk)) {
+ scp->flowloc_sw = DN_SEND;
+ dn_nsp_send_lnk(sk, DN_SEND);
+ }
+ }
+
+ pskb = &skb->next;
+
+ if (cb->nsp_flags & 0x40) {
+ if (sk->type == SOCK_SEQPACKET) {
+ msg->msg_flags |= MSG_EOR;
+ break;
+ }
+ }
+
+ if (!(flags & MSG_WAITALL))
+ break;
+
+ if (flags & MSG_OOB)
+ break;
+
+ if (copied >= target)
+ break;
+ }
+
+ rv = copied;
+out:
+ if (rv == 0)
+ rv = (flags & MSG_PEEK) ? -sk->err : sock_error(sk);
+
+ release_sock(sk);
+
+ return rv;
+}
+
+
+static int dn_sendmsg(struct socket *sock, struct msghdr *msg, int size,
+ struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ struct dn_scp *scp = &sk->protinfo.dn;
+ int mss = scp->mss;
+ int mtu = 230 - 11; /* maximum value thats always safe */
+ struct sk_buff_head *queue = &scp->data_xmit_queue;
+ int flags = msg->msg_flags;
+ unsigned short numseg = 0;
+ int err = 0;
+ int 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;
+ unsigned char msgflg;
+ unsigned char *ptr;
+ unsigned short ack;
+ int len;
+
+ if (flags & ~(MSG_TRYHARD|MSG_OOB|MSG_DONTWAIT))
+ return -EOPNOTSUPP;
+
+ if (addr_len && (addr_len != sizeof(struct sockaddr_dn)))
+ return -EINVAL;
+
+ if (sk->zapped && dn_auto_bind(sock)) {
+ err = -EADDRNOTAVAIL;
+ goto out;
+ }
+
+ if (scp->state == DN_O) {
+ if (!addr_len || !addr) {
+ err = -ENOTCONN;
+ goto out;
+ }
+
+ if ((err = dn_connect(sock, (struct sockaddr *)addr, addr_len, (flags & MSG_DONTWAIT) ? O_NONBLOCK : 0)) < 0)
+ goto out;
+ }
+
+ lock_sock(sk);
+
+ if ((err = dn_wait_run(sk, flags)) < 0)
+ goto out;
+
+ if (sk->shutdown & SEND_SHUTDOWN) {
+ send_sig(SIGPIPE, current, 0);
+ err = -EPIPE;
+ goto out;
+ }
+
+ if ((flags & MSG_TRYHARD) && sk->dst_cache)
+ dst_negative_advice(&sk->dst_cache);
+
+ if (sk->dst_cache && sk->dst_cache->neighbour) {
+ struct dn_neigh *dn = (struct dn_neigh *)sk->dst_cache->neighbour;
+ if (dn->blksize > 230)
+ mtu = dn->blksize - 11;
+ }
+
+ /*
+ * The only difference between SEQPACKET & STREAM sockets under DECnet
+ * AFAIK is that SEQPACKET sockets set the MSG_EOR flag for the last
+ * session control message segment.
+ */
+
+ if (flags & MSG_OOB) {
+ mss = 16;
+ queue = &scp->other_xmit_queue;
+ if (size > mss) {
+ err = -EMSGSIZE;
+ goto out;
+ }
+ }
+
+ if (mss < mtu)
+ mtu = mss;
+
+ scp->persist_fxn = dn_nsp_xmit_timeout;
+
+ while(sent < size) {
+ if ((err = sock_error(sk) != 0))
+ goto out;
+
+ if (signal_pending(current)) {
+ err = -ERESTARTSYS;
+ goto out;
+ }
+
+ /*
+ * Calculate size that we wish to send.
+ */
+ len = size - sent;
+
+ if (len > mtu)
+ len = mtu;
+
+ /*
+ * Wait for queue size to go down below the window
+ * size.
+ */
+ if (skb_queue_len(queue) >= scp->snd_window) {
+ if (flags & MSG_DONTWAIT) {
+ err = -EWOULDBLOCK;
+ goto out;
+ }
+
+ SOCK_SLEEP_PRE(sk)
+
+ if (skb_queue_len(queue) >= scp->snd_window)
+ schedule();
+
+ SOCK_SLEEP_POST(sk)
+
+ continue;
+ }
+
+ /*
+ * Get a suitably sized skb.
+ */
+ skb = dn_alloc_send_skb(sk, &len, flags & MSG_DONTWAIT, &err);
+
+ if (err)
+ break;
+
+ if (!skb)
+ continue;
+
+ cb = (struct dn_skb_cb *)skb->cb;
+
+ ptr = skb_put(skb, 9);
+
+ if (memcpy_fromiovec(skb_put(skb, len), msg->msg_iov, len)) {
+ err = -EFAULT;
+ goto out;
+ }
+
+ if (flags & MSG_OOB) {
+ cb->segnum = scp->numoth++;
+ scp->numoth &= 0x0fff;
+ msgflg = 0x30;
+ ack = scp->ackxmt_oth | 0x8000;
+ } else {
+ cb->segnum = scp->numdat++;
+ scp->numdat &= 0x0fff;
+ if (sock->type == SOCK_STREAM)
+ msgflg = 0x60;
+ else
+ msgflg = 0x00;
+ if (numseg == 0)
+ msgflg |= 0x20;
+ if ((sent + len) == size)
+ msgflg |= 0x40;
+ ack = scp->ackxmt_dat | 0x8000;
+ }
+
+ *ptr++ = msgflg;
+ *(__u16 *)ptr = scp->addrrem;
+ ptr += 2;
+ *(__u16 *)ptr = scp->addrloc;
+ ptr += 2;
+ *(__u16 *)ptr = dn_htons(ack);
+ ptr += 2;
+ *(__u16 *)ptr = dn_htons(cb->segnum);
+
+ sent += len;
+ dn_nsp_queue_xmit(sk, skb, flags & MSG_OOB);
+ numseg++;
+ skb = NULL;
+
+ scp->persist = dn_nsp_persist(sk);
+
+ }
+out:
+
+ if (skb)
+ kfree_skb(skb);
+
+ release_sock(sk);
+
+ return sent ? sent : err;
+}
+
+static int dn_device_event(struct notifier_block *this, unsigned long event,
+ void *ptr)
+{
+ struct device *dev = (struct 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 = {
+ dn_device_event,
+ 0
+};
+
+extern int dn_route_rcv(struct sk_buff *, struct device *, struct packet_type *);
+
+static struct packet_type dn_dix_packet_type =
+{
+ __constant_htons(ETH_P_DNA_RT),
+ NULL, /* All devices */
+ dn_route_rcv,
+ NULL,
+ NULL,
+};
+
+#ifdef CONFIG_PROC_FS
+struct proc_dir_entry decnet_linkinfo = {
+ PROC_NET_DN_SKT, 6, "decnet", S_IFREG | S_IRUGO,
+ 1, 0, 0, 0, &proc_net_inode_operations, dn_get_info
+};
+
+#ifdef CONFIG_DECNET_RAW
+
+extern int dn_raw_get_info(char *, char **, off_t, int, int);
+
+struct proc_dir_entry decnet_rawinfo = {
+ PROC_NET_DN_RAW, 10, "decnet_raw", S_IFREG | S_IRUGO,
+ 1, 0, 0, 0, &proc_net_inode_operations, dn_raw_get_info
+};
+
+#endif /* CONFIG_DECNET_RAW */
+#endif /* CONFIG_PROC_FS */
+static struct net_proto_family dn_family_ops = {
+ AF_DECnet,
+ dn_create
+};
+
+static struct proto_ops dn_proto_ops = {
+ AF_DECnet,
+
+ sock_no_dup,
+ dn_release,
+ dn_bind,
+ dn_connect,
+ sock_no_socketpair,
+ dn_accept,
+ dn_getname,
+ dn_poll,
+ dn_ioctl,
+ dn_listen,
+ dn_shutdown,
+ dn_setsockopt,
+ dn_getsockopt,
+ sock_no_fcntl,
+ dn_sendmsg,
+ dn_recvmsg
+};
+
+#ifdef CONFIG_SYSCTL
+void dn_register_sysctl(void);
+void dn_unregister_sysctl(void);
+#endif
+
+void __init decnet_proto_init(struct net_proto *pro)
+{
+ sock_register(&dn_family_ops);
+ dev_add_pack(&dn_dix_packet_type);
+ register_netdevice_notifier(&dn_dev_notifier);
+
+#ifdef CONFIG_PROC_FS
+ proc_net_register(&decnet_linkinfo);
+#ifdef CONFIG_DECNET_RAW
+ proc_net_register(&decnet_rawinfo);
+#endif
+#endif
+ dn_dev_init();
+ dn_neigh_init();
+ dn_route_init();
+
+#ifdef CONFIG_DECNET_FW
+ dn_fw_init();
+#endif /* CONFIG_DECNET_FW */
+
+#ifdef CONFIG_DECNET_ROUTER
+ dn_fib_init();
+#endif /* CONFIG_DECNET_ROUTER */
+
+#ifdef CONFIG_SYSCTL
+ dn_register_sysctl();
+#endif /* CONFIG_SYSCTL */
+ printk(KERN_INFO "DECnet for Linux: V.2.2.5s (C) 1995-1999 Linux DECnet Project Team\n");
+
+}
+
+void __init decnet_setup(char *str, int *ints)
+{
+
+ if ((ints[0] == 2) || (ints[0] == 3)) {
+
+ if (ints[1] < 0)
+ ints[1] = 0;
+ if (ints[1] > 63)
+ ints[1] = 63;
+
+ if (ints[2] < 0)
+ ints[2] = 0;
+ if (ints[2] > 1023)
+ ints[2] = 1023;
+
+ decnet_address = dn_htons(ints[1] << 10 | ints[2]);
+ dn_dn2eth(decnet_ether_address, dn_ntohs(decnet_address));
+
+ if (ints[0] == 3) {
+ switch(ints[3]) {
+#ifdef CONFIG_DECNET_ROUTER
+ case 1:
+ decnet_node_type = DN_RT_INFO_L1RT;
+ break;
+ case 2:
+ decnet_node_type = DN_RT_INFO_L2RT;
+ break;
+#endif /* CONFIG_DECNET_ROUTER */
+ default:
+ decnet_node_type = DN_RT_INFO_ENDN;
+ }
+ }
+ } else {
+ printk(KERN_ERR "DECnet: Invalid command line options\n");
+ }
+}
+
+#ifdef MODULE
+EXPORT_NO_SYMBOLS;
+MODULE_DESCRIPTION("The Linux DECnet Network Protocol");
+MODULE_AUTHOR("Linux DECnet Project Team");
+
+static int addr[2] = {0, 0};
+#ifdef CONFIG_DECNET_ROUTER
+static int type = 0;
+#endif
+
+MODULE_PARM(addr, "2i");
+MODULE_PARM_DESC(addr, "The DECnet address of this machine: area,node");
+#ifdef CONFIG_DECNET_ROUTER
+MODULE_PARM(type, "i");
+MODULE_PARM_DESC(type, "The type of this DECnet node: 0=EndNode, 1,2=Router");
+#endif
+
+int init_module(void)
+{
+ if (addr[0] > 63 || addr[0] < 0) {
+ printk(KERN_ERR "DECnet: Area must be between 0 and 63");
+ return 1;
+ }
+
+ if (addr[1] > 1023 || addr[1] < 0) {
+ printk(KERN_ERR "DECnet: Node must be between 0 and 1023");
+ return 1;
+ }
+
+ decnet_address = dn_htons((addr[0] << 10) | addr[1]);
+ dn_dn2eth(decnet_ether_address, dn_ntohs(decnet_address));
+
+#ifdef CONFIG_DECNET_ROUTER
+ switch(type) {
+ case 0:
+ decnet_node_type = DN_RT_INFO_ENDN;
+ break;
+ case 1:
+ decnet_node_type = DN_RT_INFO_L1RT;
+ break;
+ case 2:
+ decnet_node_type = DN_RT_INFO_L2RT;
+ break;
+ default:
+ printk(KERN_ERR "DECnet: Node type must be between 0 and 2 inclusive\n");
+ return 1;
+ }
+#else
+ decnet_node_type = DN_RT_INFO_ENDN;
+#endif
+
+ decnet_proto_init(NULL);
+
+ return 0;
+}
+
+void cleanup_module(void)
+{
+#ifdef CONFIG_SYSCTL
+ dn_unregister_sysctl();
+#endif /* CONFIG_SYSCTL */
+
+ unregister_netdevice_notifier(&dn_dev_notifier);
+
+ dn_route_cleanup();
+ dn_neigh_cleanup();
+ dn_dev_cleanup();
+
+#ifdef CONFIG_DECNET_FW
+ /* dn_fw_cleanup(); */
+#endif /* CONFIG_DECNET_FW */
+
+#ifdef CONFIG_DECNET_ROUTER
+ dn_fib_cleanup();
+#endif /* CONFIG_DECNET_ROUTER */
+
+#ifdef CONFIG_PROC_FS
+ proc_net_unregister(PROC_NET_DN_SKT);
+#ifdef CONFIG_DECNET_RAW
+ proc_net_unregister(PROC_NET_DN_RAW);
+#endif
+#endif
+
+ dev_remove_pack(&dn_dix_packet_type);
+ sock_unregister(AF_DECnet);
+}
+
+#endif
diff --git a/net/decnet/dn_dev.c b/net/decnet/dn_dev.c
new file mode 100644
index 000000000..e46fdbcc0
--- /dev/null
+++ b/net/decnet/dn_dev.c
@@ -0,0 +1,1386 @@
+/*
+ * 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
+ */
+
+#include <linux/config.h>
+#include <linux/net.h>
+#include <linux/netdevice.h>
+#include <linux/proc_fs.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/if_arp.h>
+#include <linux/if_ether.h>
+#include <linux/init.h>
+#include <linux/skbuff.h>
+#include <linux/rtnetlink.h>
+#include <linux/sysctl.h>
+#include <asm/uaccess.h>
+#include <net/neighbour.h>
+#include <net/dst.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;
+
+struct device *decnet_default_device = NULL;
+
+static struct dn_dev *dn_dev_create(struct device *dev, int *err);
+static void dn_dev_delete(struct device *dev);
+#ifdef CONFIG_RTNETLINK
+static void rtmsg_ifa(int event, struct dn_ifaddr *ifa);
+#endif
+
+static int dn_eth_up(struct device *);
+static void dn_send_brd_hello(struct device *dev);
+static void dn_send_ptp_hello(struct device *dev);
+static int dn_dev_eth_setsrc(struct sk_buff *skb);
+static int dn_dev_lo_setsrc(struct sk_buff *skb);
+static int dn_dev_ptp_setsrc(struct sk_buff *skb);
+static int dn_dev_eth_neigh_setup(struct neighbour *n);
+
+static struct dn_dev_parms dn_dev_list[] = {
+{
+ ARPHRD_ETHER, /* Ethernet */
+ DN_DEV_BCAST,
+ DN_DEV_S_RU,
+ 1,
+ 1498,
+ 10,
+ 1,
+ 10,
+ 0,
+ "ethernet",
+ NET_DECNET_CONF_ETHER,
+ dn_eth_up,
+ NULL,
+ NULL,
+ dn_send_brd_hello,
+ dn_dev_eth_setsrc,
+ dn_dev_eth_neigh_setup,
+ NULL
+},
+{
+ ARPHRD_IPGRE, /* DECnet tunneled over GRE in IP */
+ DN_DEV_BCAST,
+ DN_DEV_S_RU,
+ 2,
+ 1400,
+ 10,
+ 1,
+ 10,
+ 0,
+ "ipgre",
+ NET_DECNET_CONF_GRE,
+ NULL,
+ NULL,
+ NULL,
+ dn_send_brd_hello,
+ NULL,
+ NULL,
+ NULL
+},
+#if 0
+{
+ ARPHRD_X25, /* Bog standard X.25 */
+ DN_DEV_UCAST,
+ DN_DEV_S_DS,
+ 5,
+ 230,
+ 10 * 60,
+ 1,
+ 120,
+ 0,
+ "x25",
+ NET_DECNET_CONF_X25,
+ NULL,
+ NULL,
+ NULL,
+ dn_send_ptp_hello,
+ dn_dev_ptp_setsrc,
+ NULL,
+ NULL
+},
+#endif
+#if 0
+{
+ ARPHRD_PPP, /* DECnet over PPP */
+ DN_DEV_BCAST,
+ DN_DEV_S_RU,
+ 5,
+ 230,
+ 10,
+ 1,
+ 10,
+ 0,
+ "ppp",
+ NET_DECNET_CONF_PPP,
+ NULL,
+ NULL,
+ NULL,
+ dn_send_brd_hello,
+ dn_dev_ptp_setsrc,
+ NULL,
+ NULL
+},
+#endif
+#if 0
+{
+ ARPHRD_DDCMP, /* DECnet over DDCMP */
+ DN_DEV_UCAST,
+ DN_DEV_S_DS,
+ 5,
+ 230,
+ 10 * 60,
+ 1,
+ 120,
+ 0,
+ "ddcmp",
+ NET_DECNET_CONF_DDCMP,
+ NULL,
+ NULL,
+ NULL,
+ dn_send_ptp_hello,
+ dn_dev_ptp_setsrc,
+ NULL,
+ NULL
+},
+#endif
+{
+ ARPHRD_LOOPBACK, /* Loopback interface - always last */
+ DN_DEV_BCAST,
+ DN_DEV_S_RU,
+ 0,
+ 1498,
+ 10,
+ 1,
+ 10,
+ 0,
+ "loopback",
+ NET_DECNET_CONF_LOOPBACK,
+ NULL,
+ NULL,
+ NULL,
+ dn_send_brd_hello,
+ dn_dev_lo_setsrc,
+ dn_dev_eth_neigh_setup,
+ NULL
+}
+};
+
+#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 */
+#ifdef CONFIG_DECNET_ROUTER
+static int min_t1[] = { 1 };
+static int max_t1[] = { 8191 }; /* No max specified, so made it the same as t3 */
+static int min_cost[] = { 0 };
+static int max_cost[] = { 25 }; /* From DECnet spec */
+static int min_priority[] = { 0 };
+static int max_priority[] = { 127 }; /* From DECnet spec */
+#endif /* CONFIG_DECNET_ROUTER */
+
+static struct dn_dev_sysctl_table {
+ struct ctl_table_header *sysctl_header;
+#ifdef CONFIG_DECNET_ROUTER
+ ctl_table dn_dev_vars[6];
+#else
+ ctl_table dn_dev_vars[3];
+#endif
+ 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,
+ {
+#ifdef CONFIG_DECNET_ROUTER
+ {NET_DECNET_CONF_DEV_COST, "cost", (void *)DN_DEV_PARMS_OFFSET(cost),
+ sizeof(int), 0644, NULL,
+ proc_dointvec_minmax, sysctl_intvec,
+ NULL, &min_cost, &max_cost},
+ {NET_DECNET_CONF_DEV_PRIORITY, "priority", (void *)DN_DEV_PARMS_OFFSET(priority),
+ sizeof(int), 0644, NULL,
+ proc_dointvec_minmax, sysctl_intvec,
+ NULL, &min_priority, &max_priority},
+ {NET_DECNET_CONF_DEV_T1, "t1", (void *)DN_DEV_PARMS_OFFSET(t1),
+ sizeof(int), 0644, NULL,
+ proc_dointvec_minmax, sysctl_intvec,
+ NULL, &min_t1, &max_t1},
+#endif
+ {NET_DECNET_CONF_DEV_T2, "t2", (void *)DN_DEV_PARMS_OFFSET(t2),
+ sizeof(int), 0644, NULL,
+ proc_dointvec_minmax, sysctl_intvec,
+ NULL, &min_t2, &max_t2},
+ {NET_DECNET_CONF_DEV_T3, "t3", (void *)DN_DEV_PARMS_OFFSET(t3),
+ sizeof(int), 0644, NULL,
+ proc_dointvec_minmax, sysctl_intvec,
+ NULL, &min_t3, &max_t3},
+ {0}
+ },
+ {{0, "", NULL, 0, 0555, dn_dev_sysctl.dn_dev_vars}, {0}},
+ {{NET_DECNET_CONF, "conf", NULL, 0, 0555, dn_dev_sysctl.dn_dev_dev}, {0}},
+ {{NET_DECNET, "decnet", NULL, 0, 0555, dn_dev_sysctl.dn_dev_conf_dir}, {0}},
+ {{CTL_NET, "net", NULL, 0, 0555, dn_dev_sysctl.dn_dev_proto_dir}, {0}}
+};
+
+static void dn_dev_sysctl_register(struct 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 < (sizeof(t->dn_dev_vars)/sizeof(t->dn_dev_vars[0]) - 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->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);
+ }
+}
+#endif
+
+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_s(ifa, sizeof(*ifa));
+}
+
+static void dn_dev_del_ifa(struct dn_dev *dn_db, struct dn_ifaddr **ifap, int destroy)
+{
+ struct dn_ifaddr *ifa1 = *ifap;
+
+ *ifap = ifa1->ifa_next;
+
+#ifdef CONFIG_RTNETLINK
+ rtmsg_ifa(RTM_DELADDR, ifa1);
+#endif /* CONFIG_RTNETLINK */
+
+ 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)
+{
+ /*
+ * FIXME: Duplicate check here.
+ */
+
+ ifa->ifa_next = dn_db->ifa_list;
+ dn_db->ifa_list = ifa;
+
+#ifdef CONFIG_RTNETLINK
+ rtmsg_ifa(RTM_NEWADDR, ifa);
+#endif /* CONFIG_RTNETLINK */
+
+ return 0;
+}
+
+static int dn_dev_set_ifa(struct device *dev, struct dn_ifaddr *ifa)
+{
+ struct dn_dev *dn_db = dev->dn_ptr;
+
+ 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;
+
+ return dn_dev_insert_ifa(dn_db, ifa);
+}
+
+static struct dn_dev *dn_dev_by_index(int ifindex)
+{
+ struct device *dev;
+ dev = dev_get_by_index(ifindex);
+ if (dev)
+ return dev->dn_ptr;
+
+ return NULL;
+}
+
+
+int dn_dev_ioctl(unsigned int cmd, void *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 device *dev;
+ struct dn_ifaddr *ifa = NULL, **ifap = NULL;
+ int exclusive = 0;
+ 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;
+ rtnl_lock();
+ exclusive = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if ((dev = dev_get(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 = dn_saddr2dn(sdn);
+
+ ret = dn_dev_set_ifa(dev, ifa);
+ }
+done:
+ if (exclusive)
+ rtnl_unlock();
+
+ return ret;
+rarok:
+ if (copy_to_user(arg, ifr, DN_IFREQ_SIZE))
+ return -EFAULT;
+
+ return 0;
+}
+
+#ifdef CONFIG_RTNETLINK
+
+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] && strcmp(RTA_DATA(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 device *dev;
+ struct dn_dev *dn_db;
+ struct ifaddrmsg *ifm = NLMSG_DATA(nlh);
+ struct dn_ifaddr *ifa;
+
+ 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;
+
+ memcpy(&ifa->ifa_local, RTA_DATA(rta[IFA_LOCAL-1]), 2);
+ ifa->ifa_flags = ifm->ifa_flags;
+ ifa->ifa_scope = ifm->ifa_scope;
+ ifa->ifa_dev = dn_db;
+ if (rta[IFA_LABEL-1])
+ memcpy(ifa->ifa_label, RTA_DATA(rta[IFA_LABEL-1]), IFNAMSIZ);
+ else
+ memcpy(ifa->ifa_label, dev->name, IFNAMSIZ);
+
+ return dn_dev_insert_ifa(dn_db, ifa);
+}
+
+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 = 0;
+ ifm->ifa_flags = ifa->ifa_flags | IFA_F_PERMANENT;
+ ifm->ifa_scope = ifa->ifa_scope;
+ ifm->ifa_index = ifa->ifa_dev->dev->ifindex;
+ 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_fill_ifinfo(struct sk_buff *skb, struct device *dev,
+ int type, u32 pid, u32 seq)
+{
+ struct ifinfomsg *r;
+ struct nlmsghdr *nlh;
+ unsigned char *b = skb->tail;
+ struct dn_dev *dn_db = (struct dn_dev *)dev->dn_ptr;
+ unsigned char priority = dn_db->parms.priority;
+ unsigned short cost = dn_db->parms.cost;
+
+ nlh = NLMSG_PUT(skb, pid, seq, type, sizeof(*r));
+ if (pid) nlh->nlmsg_flags |= NLM_F_MULTI;
+ r = NLMSG_DATA(nlh);
+
+ r->ifi_family = AF_DECnet;
+ r->ifi_type = dev->type;
+ r->ifi_index = dev->ifindex;
+ r->ifi_flags = dev->flags;
+ r->ifi_change = ~0U;
+
+ RTA_PUT(skb, IFLA_IFNAME, strlen(dev->name)+1, dev->name);
+ RTA_PUT(skb, IFLA_COST, sizeof(unsigned short), &cost);
+ RTA_PUT(skb, IFLA_PRIORITY, sizeof(unsigned char), &priority);
+
+ nlh->nlmsg_len = skb->tail - b;
+ return skb->len;
+
+nlmsg_failure:
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+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 device *dev;
+ struct dn_dev *dn_db;
+ struct dn_ifaddr *ifa;
+
+ s_idx = cb->args[0];
+ s_dn_idx = dn_idx = cb->args[1];
+ 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:
+ cb->args[0] = idx;
+ cb->args[1] = dn_idx;
+
+ return skb->len;
+}
+
+static int dn_dev_dump_ifinfo(struct sk_buff *skb, struct netlink_callback *cb)
+{
+ int idx;
+ int s_idx = cb->args[0];
+ struct device *dev;
+
+ for(dev=dev_base, idx=0; dev; dev = dev->next) {
+ if (!dev->dn_ptr)
+ continue;
+ idx++;
+ if (idx < s_idx)
+ continue;
+ if (dn_dev_fill_ifinfo(skb, dev, RTM_NEWLINK, NETLINK_CB(cb->skb).pid, cb->nlh->nlmsg_seq) <= 0)
+ break;
+ }
+ cb->args[0] = idx;
+
+ return skb->len;
+}
+
+static void dn_dev_ifinfo(int type, struct device *dev)
+{
+ struct sk_buff *skb;
+ int size = NLMSG_GOODSIZE;
+
+ skb = alloc_skb(size, GFP_KERNEL);
+ if (!skb)
+ return;
+
+ if (dn_dev_fill_ifinfo(skb, dev, type, 0, 0) < 0) {
+ kfree_skb(skb);
+ return;
+ }
+
+ NETLINK_CB(skb).dst_groups = RTMGRP_LINK;
+ netlink_broadcast(rtnl, skb, 0, NETLINK_CB(skb).dst_groups, GFP_KERNEL);
+}
+#endif /* CONFIG_RTNETLINK */
+
+static void dn_send_endnode_hello(struct device *dev)
+{
+ 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);
+ memcpy(msg->id, decnet_ether_address, 6);
+ msg->iinfo = DN_RT_INFO_ENDN;
+ msg->blksize = dn_htons(dn_db->parms.blksize);
+ 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;
+ memcpy(msg->neighbor, dn->addr, ETH_ALEN);
+ }
+
+ 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;
+
+ if (dev->hard_header(skb,dev, ETH_P_DNA_RT,dn_rt_all_rt_mcast,
+ decnet_ether_address, skb->len) >= 0)
+ dn_send_skb(skb);
+ else
+ kfree_skb(skb);
+}
+
+
+#ifdef CONFIG_DECNET_ROUTER
+
+#define DRDELAY (5 * HZ)
+
+static int dn_am_i_a_router(struct dn_neigh *dn, struct dn_dev *dn_db)
+{
+ /* 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_eth2dn(dn->addr)) < dn_ntohs(decnet_address))
+ return 1;
+
+ return 0;
+}
+
+static void dn_send_router_hello(struct device *dev)
+{
+ 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;
+
+ if (dn_db->parms.blksize < (26 + 7))
+ return;
+
+ n = dn_db->parms.blksize - 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;
+ memcpy(ptr, decnet_ether_address, ETH_ALEN);
+ ptr += ETH_ALEN;
+ *ptr++ = (unsigned char)decnet_node_type;
+ *((unsigned short *)ptr) = dn_htons(dn_db->parms.blksize);
+ ptr += 2;
+ *ptr++ = 0; /* 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 */
+ i2 = ptr++;
+
+ n = dn_neigh_elist(dev, ptr, n);
+
+ *i2 = 7 * n;
+ *i1 = 8 + *i2;
+
+ skb_trim(skb, (26 + *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)) {
+ struct sk_buff *skb2 = skb_copy(skb, GFP_ATOMIC);
+ if (skb2) {
+ if (dev->hard_header(skb2, dev, ETH_P_DNA_RT,
+ dn_rt_all_end_mcast,
+ decnet_ether_address,
+ skb2->len) >= 0)
+ dn_send_skb(skb2);
+ else
+ kfree_skb(skb2);
+ }
+ }
+
+ if (dev->hard_header(skb, dev, ETH_P_DNA_RT, dn_rt_all_rt_mcast,
+ decnet_ether_address, skb->len) >= 0)
+ dn_send_skb(skb);
+ else
+ kfree_skb(skb);
+}
+
+static void dn_send_brd_hello(struct device *dev)
+{
+ if (decnet_node_type == DN_RT_INFO_ENDN)
+ dn_send_endnode_hello(dev);
+ else
+ dn_send_router_hello(dev);
+}
+#else
+static void dn_send_brd_hello(struct device *dev)
+{
+ dn_send_endnode_hello(dev);
+}
+#endif
+
+static void dn_send_ptp_hello(struct device *dev)
+{
+ int tdlen = 16;
+ int size = dev->hard_header_len + 2 + 4 + tdlen;
+ struct sk_buff *skb = dn_alloc_skb(NULL, size, GFP_ATOMIC);
+ struct dn_dev *dn_db = dev->dn_ptr;
+ unsigned char *ptr;
+ int i;
+
+ 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) = decnet_address;
+ ptr += 2;
+ *ptr++ = tdlen;
+
+ for(i = 0; i < tdlen; i++)
+ *ptr++ = 0252;
+
+ if (dn_db->router) {
+ struct dn_neigh *dn = (struct dn_neigh *)dn_db->router;
+ if (memcmp(dn->addr, decnet_ether_address, ETH_ALEN) == 0) {
+ struct sk_buff *skb2 = skb_clone(skb, GFP_ATOMIC);
+ if (skb2 && dev->hard_header(skb2, dev, ETH_P_DNA_RT,
+ dn_rt_all_end_mcast,
+ decnet_ether_address,
+ skb->len) >= 0) {
+
+ dn_send_skb(skb2);
+ }
+ }
+ }
+
+ if (dev->hard_header(skb, dev, ETH_P_DNA_RT, dn_rt_all_rt_mcast,
+ decnet_ether_address, skb->len) < 0);
+ return;
+
+ dn_send_skb(skb);
+}
+
+static int dn_eth_up(struct device *dev)
+{
+ struct dn_dev *dn_db = dev->dn_ptr;
+
+ if (decnet_node_type == DN_RT_INFO_ENDN)
+ dev_mc_add(dev, dn_rt_all_end_mcast, ETH_ALEN, 0);
+
+ if (decnet_node_type == DN_RT_INFO_L1RT || decnet_node_type == DN_RT_INFO_L2RT)
+ dev_mc_add(dev, dn_rt_all_rt_mcast, ETH_ALEN, 0);
+
+ dev_mc_upload(dev);
+
+ dn_db->use_long = 1;
+
+ return 0;
+}
+
+static int dn_dev_eth_setsrc(struct sk_buff *skb)
+{
+ struct ethhdr *h = skb->mac.ethernet;
+ struct dn_skb_cb *cb = (struct dn_skb_cb *)skb->cb;
+
+ if (h == NULL)
+ return -1;
+
+ cb->neigh = dn_eth2dn(h->h_source);
+
+ return 0;
+}
+
+static int dn_dev_lo_setsrc(struct sk_buff *skb)
+{
+ struct dn_skb_cb *cb = (struct dn_skb_cb *)skb->cb;
+
+ cb->neigh = decnet_address;
+
+ return 0;
+}
+
+static int dn_dev_eth_neigh_setup(struct neighbour *n)
+{
+ struct dn_neigh *dn = (struct dn_neigh *)n;
+
+ memcpy(n->ha, dn->addr, ETH_ALEN);
+
+ return 0;
+}
+
+static int dn_dev_ptp_setsrc(struct sk_buff *skb)
+{
+ struct device *dev = skb->dev;
+ struct dn_dev *dn_db = dev->dn_ptr;
+ struct dn_skb_cb *cb = (struct dn_skb_cb *)skb->cb;
+
+ if (!dn_db->peer)
+ return -1;
+
+ cb->neigh = dn_eth2dn(((struct dn_neigh *)dn_db->peer)->addr);
+
+ return 0;
+}
+
+static void dn_dev_set_timer(struct device *dev);
+
+static void dn_dev_timer_func(unsigned long arg)
+{
+ struct device *dev = (struct device *)arg;
+ struct dn_dev *dn_db = dev->dn_ptr;
+
+#ifdef CONFIG_DECNET_ROUTER
+ if (decnet_node_type == DN_RT_INFO_L1RT || decnet_node_type == DN_RT_INFO_L2RT) {
+ if (dn_db->t1 <= dn_db->parms.t2) {
+ if (dn_db->parms.timer1)
+ dn_db->parms.timer1(dev);
+ dn_db->t1 = dn_db->parms.t1;
+ } else {
+ dn_db->t1 -= dn_db->parms.t2;
+ }
+ }
+#endif /* CONFIG_DECNET_ROUTER */
+
+ if (dn_db->t3 <= dn_db->parms.t2) {
+ if (dn_db->parms.timer3)
+ dn_db->parms.timer3(dev);
+ 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 device *dev)
+{
+ struct dn_dev *dn_db = dev->dn_ptr;
+
+#ifdef CONFIG_DECNET_ROUTER
+ if (dn_db->parms.t2 > dn_db->parms.t1)
+ dn_db->parms.t2 = dn_db->parms.t1;
+#endif
+
+ 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 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));
+ dev->dn_ptr = dn_db;
+ dn_db->dev = dev;
+ init_timer(&dn_db->timer);
+
+ memcpy(dn_db->addr, decnet_ether_address, ETH_ALEN); /* To go... */
+
+ 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_db->neigh_parms->neigh_setup = dn_db->parms.neigh_setup;
+
+ dn_dev_sysctl_register(dev, &dn_db->parms);
+
+ dn_dev_set_timer(dev);
+
+#ifdef CONFIG_RTNETLINK
+ dn_dev_ifinfo(RTM_NEWLINK, dev);
+#endif
+
+ *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.
+ */
+void dn_dev_up(struct device *dev)
+{
+ struct dn_ifaddr *ifa;
+
+ if ((dev->type != ARPHRD_ETHER) && (dev->type != ARPHRD_LOOPBACK))
+ return;
+
+ if (dev->type == ARPHRD_ETHER)
+ if (memcmp(dev->dev_addr, decnet_ether_address, ETH_ALEN) != 0)
+ return;
+
+ if ((ifa = dn_dev_alloc_ifa()) == NULL)
+ return;
+
+ ifa->ifa_local = decnet_address;
+ ifa->ifa_flags = 0;
+ ifa->ifa_scope = RT_SCOPE_UNIVERSE;
+ strcpy(ifa->ifa_label, dev->name);
+
+ dn_dev_set_ifa(dev, ifa);
+}
+
+static void dn_dev_delete(struct device *dev)
+{
+ struct dn_dev *dn_db = dev->dn_ptr;
+ unsigned long cpuflags;
+
+ if (dn_db == NULL)
+ return;
+
+ save_flags(cpuflags);
+ cli();
+ del_timer(&dn_db->timer);
+ restore_flags(cpuflags);
+
+#ifdef CONFIG_RTNETLINK
+ dn_dev_ifinfo(RTM_DELLINK, dev);
+#endif
+
+ dn_dev_sysctl_unregister(&dn_db->parms);
+
+ neigh_ifdown(&dn_neigh_table, dev);
+
+ if (dev == decnet_default_device)
+ decnet_default_device = NULL;
+
+ if (dn_db->parms.down)
+ dn_db->parms.down(dev);
+
+ dev->dn_ptr = NULL;
+
+ neigh_parms_release(&dn_neigh_table, dn_db->neigh_parms);
+
+ 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 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 device *dev;
+
+ for(dev = dev_base; dev; dev = dev->next)
+ dn_dev_down(dev);
+
+}
+
+void dn_dev_devices_on(void)
+{
+ struct device *dev;
+
+ for(dev = dev_base; dev; dev = dev->next) {
+ if (dev->flags & IFF_UP)
+ dn_dev_up(dev);
+ }
+}
+
+
+#ifdef CONFIG_DECNET_SIOCGIFCONF
+/*
+ * Now we support multiple addresses per interface.
+ * Since we don't want to break existing code, you have to enable
+ * it as a compile time option. Probably you should use the
+ * rtnetlink interface instead.
+ */
+int dnet_gifconf(struct device *dev, char *buf, int len)
+{
+ struct dn_dev *dn_db = (struct dn_dev *)dev->dn_ptr;
+ struct dn_ifaddr *ifa;
+ struct ifreq *ifr = (struct ifreq *)buf;
+ int done = 0;
+
+ if ((dn_db == NULL) || ((ifa = dn_db->ifa_list) == NULL))
+ return 0;
+
+ for(; ifa; ifa = ifa->ifa_next) {
+ if (!ifr) {
+ done += sizeof(DN_IFREQ_SIZE);
+ continue;
+ }
+ if (len < DN_IFREQ_SIZE)
+ return done;
+ memset(ifr, 0, DN_IFREQ_SIZE);
+
+ if (ifa->ifa_label)
+ strcpy(ifr->ifr_name, ifa->ifa_label);
+ else
+ strcpy(ifr->ifr_name, dev->name);
+
+ (*(struct sockaddr_dn *) &ifr->ifr_addr).sdn_family = AF_DECnet;
+ (*(struct sockaddr_dn *) &ifr->ifr_addr).sdn_add.a_len = 2;
+ (*(dn_address *)(*(struct sockaddr_dn *) &ifr->ifr_addr).sdn_add.a_addr) = ifa->ifa_local;
+
+ ifr = (struct ifreq *)((char *)ifr + DN_IFREQ_SIZE);
+ len -= DN_IFREQ_SIZE;
+ done += DN_IFREQ_SIZE;
+ }
+
+ return done;
+}
+#endif /* CONFIG_DECNET_SIOCGIFCONF */
+
+
+#ifdef CONFIG_PROC_FS
+
+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 decnet_dev_get_info(char *buffer, char **start, off_t offset, int length, int dummy)
+{
+ struct dn_dev *dn_db;
+ struct device *dev;
+ int len = 0;
+ off_t pos = 0;
+ off_t begin = 0;
+ char peer_buf[DN_ASCBUF_LEN];
+ char router_buf[DN_ASCBUF_LEN];
+
+ cli();
+
+ len += sprintf(buffer, "Name Flags T1 Timer1 T3 Timer3 BlkSize Pri State DevType Router Peer\n");
+
+ for (dev = dev_base; dev; dev = dev->next) {
+ if ((dn_db = (struct dn_dev *)dev->dn_ptr) == NULL)
+ continue;
+
+ len += sprintf(buffer + len, "%-8s %1s %04lu %04lu %04lu %04lu %04hu %03d %02x %-10s %-7s %-7s\n",
+ dev->name ? dev->name : "???",
+ dn_type2asc(dn_db->parms.mode),
+ dn_db->t1, dn_db->parms.t1,
+ dn_db->t3, dn_db->parms.t3,
+ dn_db->parms.blksize,
+ dn_db->parms.priority,
+ dn_db->parms.state, dn_db->parms.name,
+ dn_db->router ? dn_addr2asc(dn_eth2dn(dn_db->router->primary_key), router_buf) : "",
+ dn_db->peer ? dn_addr2asc(dn_eth2dn(dn_db->peer->primary_key), peer_buf) : "");
+
+
+ pos = begin + len;
+
+ if (pos < offset) {
+ len = 0;
+ begin = pos;
+ }
+ if (pos > offset + length)
+ break;
+ }
+
+ sti();
+
+ *start = buffer + (offset - begin);
+ len -= (offset - begin);
+
+ if (len > length) len = length;
+
+ return(len);
+}
+
+static struct proc_dir_entry proc_net_decnet_dev = {
+ PROC_NET_DN_DEV, 10, "decnet_dev",
+ S_IFREG | S_IRUGO, 1, 0, 0,
+ 0, &proc_net_inode_operations,
+ decnet_dev_get_info
+};
+
+#endif /* CONFIG_PROC_FS */
+
+#ifdef CONFIG_RTNETLINK
+static struct rtnetlink_link dnet_rtnetlink_table[RTM_MAX-RTM_BASE+1] =
+{
+ { NULL, NULL, },
+ { NULL, NULL, },
+ { NULL, dn_dev_dump_ifinfo, },
+ { NULL, NULL, },
+
+ { dn_dev_rtm_newaddr, NULL, },
+ { dn_dev_rtm_deladdr, NULL, },
+ { NULL, dn_dev_dump_ifaddr, },
+ { NULL, NULL, },
+#ifdef CONFIG_DECNET_ROUTER
+ { dn_fib_rtm_newroute, NULL, },
+ { dn_fib_rtm_delroute, NULL, },
+ { dn_fib_rtm_getroute, dn_fib_dump, },
+ { NULL, NULL, },
+#else
+ { NULL, NULL, },
+ { NULL, NULL, },
+ { NULL, NULL, },
+ { NULL, NULL, },
+#endif
+ { NULL, NULL, },
+ { NULL, NULL, },
+ { NULL, NULL, },
+ { NULL, NULL, },
+
+ { NULL, NULL, },
+ { NULL, NULL, },
+ { NULL, NULL, },
+ { NULL, NULL, }
+};
+#endif /* CONFIG_RTNETLINK */
+
+void __init dn_dev_init(void)
+{
+
+ dn_dev_devices_on();
+#ifdef CONFIG_DECNET_SIOCGIFCONF
+ register_gifconf(PF_DECnet, dnet_gifconf);
+#endif /* CONFIG_DECNET_SIOCGIFCONF */
+
+#ifdef CONFIG_RTNETLINK
+ rtnetlink_links[PF_DECnet] = dnet_rtnetlink_table;
+#endif /* CONFIG_RTNETLINK */
+
+#ifdef CONFIG_PROC_FS
+ proc_net_register(&proc_net_decnet_dev);
+#endif /* CONFIG_PROC_FS */
+
+#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 */
+}
+
+#if defined(CONFIG_DECNET_MODULE)
+void dn_dev_cleanup(void)
+{
+#ifdef CONFIG_RTNETLINK
+ rtnetlink_links[PF_DECnet] = NULL;
+#endif /* CONFIG_RTNETLINK */
+
+#ifdef CONFIG_DECNET_SIOCGIFCONF
+ unregister_gifconf(PF_DECnet);
+#endif /* CONFIG_DECNET_SIOCGIFCONF */
+
+#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 */
+
+#ifdef CONFIG_PROC_FS
+ proc_net_unregister(PROC_NET_DN_DEV);
+#endif /* CONFIG_PROC_FS */
+
+ dn_dev_devices_off();
+}
+#endif /* CONFIG_DECNET_MODULE */
diff --git a/net/decnet/dn_fib.c b/net/decnet/dn_fib.c
new file mode 100644
index 000000000..16f2e3e03
--- /dev/null
+++ b/net/decnet/dn_fib.c
@@ -0,0 +1,805 @@
+/*
+ * 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
+ *
+ * Author: Steve Whitehouse <SteveW@ACM.org>
+ *
+ *
+ * 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/rtnetlink.h>
+#include <asm/spinlock.h>
+#include <asm/atomic.h>
+#include <asm/uaccess.h>
+#include <net/neighbour.h>
+#include <net/dst.h>
+#include <net/dn.h>
+#include <net/dn_fib.h>
+#include <net/dn_neigh.h>
+#include <net/dn_dev.h>
+
+/*
+ * N.B. Some of the functions here should really be inlines, but
+ * I'll sort out that when its all working properly, for now the
+ * stack frames will be useful for debugging.
+ */
+#define DN_NUM_TABLES 255
+#define DN_MIN_TABLE 1
+#define DN_L1_TABLE 1
+#define DN_L2_TABLE 2
+
+#ifdef CONFIG_RTNETLINK
+static int dn_fib_table_dump(struct dn_fib_table *t, struct sk_buff *skb, struct netlink_callback *cb);
+static void dn_rtmsg_fib(int event, int table, struct dn_fib_action *fa, struct nlmsghdr *nlh, struct netlink_skb_parms *req);
+#endif /* CONFIG_RTNETLINK */
+
+static void dn_fib_del_tree(struct dn_fib_table *t);
+
+static struct dn_fib_table *dn_fib_tables[DN_NUM_TABLES + 1];
+static int dn_fib_allocs = 0;
+static int dn_fib_actions = 0;
+
+static struct dn_fib_node *dn_fib_alloc(void)
+{
+ struct dn_fib_node *fn;
+
+ fn = kmalloc(sizeof(struct dn_fib_node), GFP_KERNEL);
+
+ if (fn) {
+ memset(fn, 0, sizeof(struct dn_fib_node));
+ dn_fib_allocs++;
+ }
+
+ return fn;
+}
+
+
+static __inline__ void dn_fib_free(struct dn_fib_node *fn)
+{
+ kfree_s(fn, sizeof(struct dn_fib_node));
+ dn_fib_allocs--;
+}
+
+static struct dn_fib_action *dn_fib_new_action(void)
+{
+ struct dn_fib_action *fa;
+
+ fa = kmalloc(sizeof(struct dn_fib_action), GFP_KERNEL);
+
+ if (fa) {
+ memset(fa, 0, sizeof(struct dn_fib_action));
+ dn_fib_actions++;
+ }
+
+ return fa;
+}
+
+static __inline__ void dn_fib_del_action(struct dn_fib_action *fa)
+{
+ if ((fa->fa_type == RTN_UNICAST) && fa->fa_neigh)
+ neigh_release(fa->fa_neigh);
+
+ kfree_s(fa, sizeof(struct dn_fib_action));
+ dn_fib_actions--;
+}
+
+static struct dn_fib_node *dn_fib_follow(struct dn_fib_node *fn, dn_address key)
+{
+ while(fn->fn_action == NULL)
+ fn = DN_FIB_NEXT(fn, key);
+
+ return fn;
+}
+
+
+static struct dn_fib_node *dn_fib_follow1(struct dn_fib_node *fn, dn_address key)
+{
+ while((fn->fn_action == NULL) && (((key ^ fn->fn_key) >> fn->fn_shift) == 0))
+ fn = DN_FIB_NEXT(fn, key);
+
+ return fn;
+}
+
+
+static int dn_fib_table_insert1(struct dn_fib_table *t, struct dn_fib_node *leaf)
+{
+ struct dn_fib_node *fn, *fn1, *fn2;
+ int shift = -1;
+ dn_address match;
+ dn_address cmpmask = 1;
+
+ if (!t->root) {
+ t->root = leaf;
+ t->count++;
+ return 0;
+ }
+
+ fn1 = dn_fib_follow1(t->root, leaf->fn_key);
+ fn2 = fn1->fn_up;
+
+ if (fn1->fn_key == leaf->fn_key)
+ return -EEXIST;
+
+ if ((fn = dn_fib_alloc()) == NULL)
+ return -ENOBUFS;
+
+ fn->fn_key = leaf->fn_key;
+ match = fn1->fn_key ^ fn->fn_key;
+
+ while(match) {
+ match >>= 1;
+ shift++;
+ }
+ cmpmask <<= shift;
+
+ fn->fn_cmpmask = cmpmask;
+ fn->fn_shift = shift;
+
+ if (fn2) {
+ DN_FIB_NEXT(fn2, fn->fn_key) = fn;
+ } else {
+ t->root = fn;
+ }
+
+ t->count++;
+ fn->fn_up = fn2;
+ DN_FIB_NEXT(fn, fn1->fn_key) = fn1;
+ DN_FIB_NEXT(fn, leaf->fn_key) = leaf;
+
+ return 0;
+}
+
+static __inline__ int dn_maskcmp(dn_address m1, dn_address m2)
+{
+ int cmp = 0;
+
+ while(m1 || m2) {
+ if (m1 & 0x8000)
+ cmp++;
+ if (m2 & 0x8000)
+ cmp--;
+ m1 <<= 1;
+ m2 <<= 1;
+ }
+
+ return cmp;
+}
+
+
+static int dn_fib_table_insert(struct dn_fib_table *t, struct dn_fib_action *fa)
+{
+ struct dn_fib_node *fn;
+ struct dn_fib_action **fap;
+ int err;
+ int cmp;
+
+ if (t->root && ((fn = dn_fib_follow(t->root, fa->fa_key)) != NULL) &&
+ (fn->fn_key == fa->fa_key))
+ goto add_action;
+
+ if ((fn = dn_fib_alloc()) == NULL)
+ return -ENOBUFS;
+
+ fn->fn_key = fa->fa_key;
+ fn->fn_action = fa;
+
+ if ((err = dn_fib_table_insert1(t, fn)) < 0)
+ dn_fib_free(fn);
+
+#ifdef CONFIG_RTNETLINK
+ if (!err)
+ dn_rtmsg_fib(RTM_NEWROUTE, t->n, fa, NULL, NULL);
+#endif /* CONFIG_RTNETLINK */
+
+ return err;
+
+add_action:
+ fap = &fn->fn_action;
+
+ for(; *fap; fap = &((*fap)->fa_next)) {
+ if ((cmp = dn_maskcmp((*fap)->fa_mask, fa->fa_mask)) > 0)
+ break;
+ if (cmp < 0)
+ continue;
+ if ((*fap)->fa_cost > fa->fa_cost)
+ break;
+ }
+
+ fa->fa_next = *fap;
+ *fap = fa;
+
+#ifdef CONFIG_RTNETLINK
+ dn_rtmsg_fib(RTM_NEWROUTE, t->n, fa, NULL, NULL);
+#endif /* CONFIG_RTNETLINK */
+
+ return 0;
+}
+
+static int dn_fib_table_delete1(struct dn_fib_table *t, struct dn_fib_node *fn)
+{
+ struct dn_fib_node *fn1 = fn->fn_up;
+ struct dn_fib_node *fn2;
+ struct dn_fib_node *fn3;
+
+ if (fn == t->root) {
+ t->root = NULL;
+ t->count--;
+ return 0;
+ }
+
+ if (fn1 == NULL)
+ return -EINVAL;
+
+ fn2 = fn1->fn_up;
+ fn3 = DN_FIB_NEXT(fn1, ~fn->fn_key);
+
+ if (fn2)
+ DN_FIB_NEXT(fn2, fn1->fn_key) = fn3;
+ else
+ t->root = fn3;
+
+ fn3->fn_up = fn2;
+
+ dn_fib_free(fn1);
+ t->count--;
+ return 0;
+}
+
+static int dn_fib_table_delete(struct dn_fib_table *t, struct dn_fib_action *fa)
+{
+ struct dn_fib_res res;
+ struct dn_fib_node *fn;
+ struct dn_fib_action **fap, *old;
+ int err;
+
+ res.res_type = 0;
+ res.res_addr = fa->fa_key;
+ res.res_mask = fa->fa_mask;
+ res.res_ifindex = fa->fa_ifindex;
+ res.res_proto = fa->fa_proto;
+ res.res_cost = fa->fa_cost;
+
+ if ((err = t->lookup(t, &res)) < 0)
+ return err;
+
+ fn = res.res_fn;
+ fap = &fn->fn_action;
+ while((*fap) != res.res_fa)
+ fap = &((*fap)->fa_next);
+ old = *fap;
+ *fap = (*fap)->fa_next;
+
+ if (fn->fn_action == NULL)
+ dn_fib_table_delete1(t, fn);
+
+ if (t->root == NULL)
+ dn_fib_del_tree(t);
+
+#ifdef CONFIG_RTNETLINK
+ dn_rtmsg_fib(RTM_DELROUTE, t->n, old, NULL, NULL);
+#endif /* CONFIG_RTNETLINK */
+
+ dn_fib_del_action(old);
+
+ return 0;
+}
+
+static int dn_fib_search(struct dn_fib_node *fn, struct dn_fib_res *res)
+{
+ struct dn_fib_action *fa = fn->fn_action;
+
+ for(; fa; fa = fa->fa_next) {
+ if ((fa->fa_key ^ res->res_addr) & fa->fa_mask)
+ continue;
+ if (res->res_ifindex && (res->res_ifindex != fa->fa_ifindex))
+ continue;
+ if (res->res_mask && (res->res_mask != fa->fa_mask))
+ continue;
+ if (res->res_proto && (res->res_proto != fa->fa_proto))
+ continue;
+ if (res->res_cost && (res->res_cost != fa->fa_cost))
+ continue;
+
+ res->res_fn = fn;
+ res->res_fa = fa;
+ return 1;
+ }
+
+ return 0;
+}
+
+static int dn_fib_recurse(struct dn_fib_node *fn, struct dn_fib_res *res)
+{
+ struct dn_fib_node *fn1;
+ int err = -ENOENT;
+
+ fn1 = dn_fib_follow(fn, res->res_addr);
+
+ if (dn_fib_search(fn1, res))
+ return 0;
+
+ while((fn1 = fn1->fn_up) != fn)
+ if ((err = dn_fib_recurse(DN_FIB_NEXT(fn1, ~res->res_addr), res)) == 0)
+ break;
+
+ return err;
+}
+
+static int dn_fib_table_lookup(struct dn_fib_table *t, struct dn_fib_res *res)
+{
+ struct dn_fib_node *fn = t->root;
+ int err = -ENOENT;
+
+ if (t->root == NULL)
+ return err;
+
+ fn = dn_fib_follow(t->root, res->res_addr);
+
+ if (dn_fib_search(fn, res))
+ return 0;
+
+ while((fn = fn->fn_up) != NULL)
+ if ((err = dn_fib_recurse(DN_FIB_NEXT(fn, ~res->res_addr), res)) == 0)
+ break;
+
+ return err;
+}
+
+static int dn_fib_table_walk_recurse(struct dn_fib_walker_t *fwt, struct dn_fib_node *fn)
+{
+ struct dn_fib_table *t = fwt->table;
+
+ if (fn->fn_action) {
+ fwt->fxn(fwt, fn);
+ } else {
+ dn_fib_table_walk_recurse(fwt, t->root->fn_children[0]);
+ dn_fib_table_walk_recurse(fwt, t->root->fn_children[1]);
+ }
+
+ return 0;
+}
+
+static int dn_fib_table_walk(struct dn_fib_walker_t *fwt)
+{
+ struct dn_fib_table *t = fwt->table;
+
+ if (t->root != NULL) {
+ if (t->root->fn_action) {
+ fwt->fxn(fwt, t->root);
+ } else {
+ dn_fib_table_walk_recurse(fwt, t->root->fn_children[0]);
+ dn_fib_table_walk_recurse(fwt, t->root->fn_children[1]);
+ }
+ }
+
+ return 0;
+}
+
+static struct dn_fib_table *dn_fib_get_tree(int n, int create)
+{
+ struct dn_fib_table *t;
+
+ if (n < DN_MIN_TABLE)
+ return NULL;
+
+ if (n > DN_NUM_TABLES)
+ return NULL;
+
+ if (dn_fib_tables[n])
+ return dn_fib_tables[n];
+
+ if (!create)
+ return NULL;
+
+ if ((t = kmalloc(sizeof(struct dn_fib_table), GFP_KERNEL)) == NULL)
+ return NULL;
+
+ dn_fib_tables[n] = t;
+ 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->walk = dn_fib_table_walk;
+#ifdef CONFIG_RTNETLINK
+ t->dump = dn_fib_table_dump;
+#endif
+
+ return t;
+}
+
+static void dn_fib_del_tree(struct dn_fib_table *t)
+{
+ dn_fib_tables[t->n] = NULL;
+
+ if (t) {
+ kfree_s(t, sizeof(struct dn_fib_table));
+ }
+}
+
+
+int dn_fib_resolve(struct dn_fib_res *res)
+{
+ int table = DN_L1_TABLE;
+ int count = 0;
+ struct dn_fib_action *fa;
+ int err;
+
+ if ((res->res_addr ^ dn_ntohs(decnet_address)) & 0xfc00)
+ table = DN_L2_TABLE;
+
+ for(;;) {
+ struct dn_fib_table *t = dn_fib_get_tree(table, 0);
+
+ if (t == NULL)
+ return -ENOBUFS;
+
+ if ((err = t->lookup(t, res)) < 0)
+ return err;
+
+ if ((fa = res->res_fa) == NULL)
+ return -ENOENT;
+
+ if (fa->fa_type != RTN_THROW)
+ break;
+
+ table = fa->fa_table;
+
+ if (count++ > DN_NUM_TABLES)
+ return -ENOENT;
+ }
+
+ return (fa->fa_type == RTN_PROHIBIT) ? -fa->fa_error : 0;
+}
+
+/*
+ * Punt to user via netlink for example, but for now
+ * we just drop it.
+ */
+int dn_fib_rt_message(struct sk_buff *skb)
+{
+ kfree_skb(skb);
+
+ return 0;
+}
+
+
+#ifdef CONFIG_RTNETLINK
+static int dn_fib_convert_rtm(struct dn_fib_action *fa,
+ struct rtmsg *r, struct rtattr **rta,
+ struct nlmsghdr *n,
+ struct netlink_skb_parms *req)
+{
+ dn_address dst, gw, mask = 0xffff;
+ int ifindex;
+ struct neighbour *neigh;
+ struct device *dev;
+ unsigned char addr[ETH_ALEN];
+
+ if (r->rtm_family != AF_DECnet)
+ return -EINVAL;
+
+ if (rta[RTA_DST-1])
+ memcpy(&dst, RTA_DATA(rta[RTA_DST-1]), 2);
+
+ if (rta[RTA_OIF-1])
+ memcpy(&ifindex, RTA_DATA(rta[RTA_OIF-1]), sizeof(int));
+
+ if (rta[RTA_GATEWAY-1])
+ memcpy(&gw, RTA_DATA(rta[RTA_GATEWAY-1]), 2);
+
+ fa->fa_key = dn_ntohs(dst);
+ fa->fa_mask = mask;
+ fa->fa_ifindex = ifindex;
+ fa->fa_proto = r->rtm_protocol;
+ fa->fa_type = r->rtm_type;
+
+ switch(fa->fa_type) {
+ case RTN_UNICAST:
+ if ((dev = dev_get_by_index(ifindex)) == NULL)
+ return -ENODEV;
+ dn_dn2eth(addr, gw);
+ if ((neigh = __neigh_lookup(&dn_neigh_table, &addr, dev, 1)) == NULL)
+ return -EHOSTUNREACH;
+ fa->fa_neigh = neigh;
+ break;
+ case RTN_THROW:
+ fa->fa_table = 0;
+ break;
+ case RTN_PROHIBIT:
+ fa->fa_error = 0;
+ break;
+ case RTN_UNREACHABLE:
+ fa->fa_error = EHOSTUNREACH;
+ break;
+ }
+
+ return 0;
+}
+
+static int dn_fib_check_attr(struct rtmsg *r, struct rtattr **rta)
+{
+ switch(r->rtm_type) {
+ case RTN_UNICAST:
+ case RTN_BLACKHOLE:
+ case RTN_PROHIBIT:
+ case RTN_UNREACHABLE:
+ case RTN_THROW:
+ break;
+ default:
+ return -1;
+ }
+
+ return 0;
+}
+
+int dn_fib_rtm_delroute(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
+{
+ struct dn_fib_table *t;
+ struct rtattr **rta = arg;
+ struct rtmsg *r = NLMSG_DATA(nlh);
+ struct dn_fib_action *fa;
+ int err;
+
+ if (dn_fib_check_attr(r, rta))
+ return -EINVAL;
+
+ if ((fa = dn_fib_new_action()) == NULL)
+ return -ENOBUFS;
+
+ t = dn_fib_get_tree(r->rtm_table, 0);
+ if (t) {
+ if ((err = dn_fib_convert_rtm(fa, r, rta, nlh, &NETLINK_CB(skb))) < 0) {
+ dn_fib_del_action(fa);
+ return err;
+ }
+ err = t->delete(t, fa);
+ dn_fib_del_action(fa);
+ return err;
+ }
+ return -ESRCH;
+}
+
+int dn_fib_rtm_newroute(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
+{
+ struct dn_fib_table *t;
+ struct rtattr **rta = arg;
+ struct rtmsg *r = NLMSG_DATA(nlh);
+ struct dn_fib_action *fa;
+ int err;
+
+ if (dn_fib_check_attr(r, rta))
+ return -EINVAL;
+
+ if ((fa = dn_fib_new_action()) == NULL)
+ return -ENOBUFS;
+
+ t = dn_fib_get_tree(r->rtm_table, 1);
+ if (t) {
+ if ((err = dn_fib_convert_rtm(fa, r, rta, nlh, &NETLINK_CB(skb))) < 0) {
+ dn_fib_del_action(fa);
+ return err;
+ }
+ return t->insert(t, fa);
+ }
+ return -ENOBUFS;
+}
+
+int dn_fib_dump_info(struct sk_buff *skb, u32 pid, u32 seq, int event,
+ int table, struct dn_fib_action *fa)
+{
+ 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 = 16;
+ rtm->rtm_src_len = 16;
+ rtm->rtm_tos = 0;
+ rtm->rtm_table = table;
+ rtm->rtm_type = fa->fa_type;
+ rtm->rtm_flags = 0;
+ rtm->rtm_protocol = fa->fa_proto;
+ RTA_PUT(skb, RTA_DST, 2, &fa->fa_key);
+ if (fa->fa_ifindex)
+ RTA_PUT(skb, RTA_OIF, sizeof(int), &fa->fa_ifindex);
+
+ 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, int table, struct dn_fib_action *fa,
+ 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, table, fa) < 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 int dn_fib_table_dump(struct dn_fib_table *t, struct sk_buff *skb, struct netlink_callback *cb)
+{
+
+ return skb->len;
+}
+
+int dn_fib_dump(struct sk_buff *skb, struct netlink_callback *cb)
+{
+ int s_t;
+ struct dn_fib_table *t;
+
+ for(s_t = cb->args[0]; s_t < DN_NUM_TABLES; s_t++) {
+ if (s_t > cb->args[0])
+ memset(&cb->args[1], 0, sizeof(cb->args)-sizeof(int));
+ t = dn_fib_get_tree(s_t, 0);
+ if (t == NULL)
+ continue;
+ if (t->dump(t, skb, cb) < 0)
+ break;
+ }
+
+ cb->args[0] = s_t;
+
+ return skb->len;
+}
+#endif /* CONFIG_RTNETLINK */
+
+int dn_fib_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
+{
+
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+
+ switch(cmd) {
+ case SIOCADDRT:
+ case SIOCDELRT:
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+struct dn_fib_procfs {
+ int len;
+ off_t pos;
+ off_t begin;
+ off_t offset;
+ int length;
+ char *buffer;
+};
+
+static int dn_proc_action_list(struct dn_fib_walker_t *fwt, struct dn_fib_node *fn)
+{
+ struct dn_fib_procfs *pinfo = (struct dn_fib_procfs *)fwt->arg;
+ struct dn_fib_action *fa;
+ char ab[DN_ASCBUF_LEN];
+
+ if (pinfo->pos > pinfo->offset + pinfo->length)
+ return 0;
+
+ for(fa = fn->fn_action; fa; fa = fa->fa_next) {
+
+ pinfo->len += sprintf(pinfo->buffer + pinfo->len,
+ "%s/%04hx %02x %02x\n",
+ dn_addr2asc(fa->fa_key, ab),
+ fa->fa_mask,
+ fa->fa_type,
+ fa->fa_proto);
+
+ pinfo->pos = pinfo->begin + pinfo->len;
+ if (pinfo->pos < pinfo->offset) {
+ pinfo->len = 0;
+ pinfo->begin = pinfo->pos;
+ }
+ if (pinfo->pos > pinfo->offset + pinfo->length)
+ break;
+ }
+
+ return 0;
+}
+
+static int decnet_rt_get_info(char *buffer, char **start, off_t offset, int length, int dummy)
+{
+ struct dn_fib_procfs pinfo;
+ int i;
+ struct dn_fib_table *t;
+ struct dn_fib_walker_t fwt;
+
+ pinfo.pos = 0;
+ pinfo.len = 0;
+ pinfo.begin = 0;
+ pinfo.offset = offset;
+ pinfo.length = length;
+ pinfo.buffer = buffer;
+
+ fwt.arg = &pinfo;
+ fwt.fxn = dn_proc_action_list;
+
+ start_bh_atomic();
+ for(i = 0; i < DN_NUM_TABLES; i++) {
+ if ((t = dn_fib_get_tree(i, 0)) == NULL)
+ continue;
+
+ fwt.table = t;
+ t->walk(&fwt);
+
+ if (pinfo.pos > pinfo.offset + pinfo.length)
+ break;
+ }
+ end_bh_atomic();
+
+ *start = pinfo.buffer + (pinfo.offset - pinfo.begin);
+ pinfo.len -= (pinfo.offset - pinfo.begin);
+
+ if (pinfo.len > pinfo.length)
+ pinfo.len = pinfo.length;
+
+ return pinfo.len;
+}
+
+static struct proc_dir_entry proc_net_decnet_route = {
+ PROC_NET_DN_ROUTE, 12, "decnet_route",
+ S_IFREG | S_IRUGO, 1, 0, 0,
+ 0, &proc_net_inode_operations,
+ decnet_rt_get_info
+};
+
+#ifdef CONFIG_DECNET_MODULE
+void dn_fib_cleanup(void)
+{
+#ifdef CONFIG_PROC_FS
+ proc_net_unregister(PROC_NET_DN_ROUTE);
+#endif /* CONFIG_PROC_FS */
+}
+#endif /* CONFIG_DECNET_MODULE */
+
+
+void __init dn_fib_init(void)
+{
+ memset(dn_fib_tables, 0, DN_NUM_TABLES * sizeof(struct dn_fib_table *));
+
+#ifdef CONFIG_PROC_FS
+ proc_net_register(&proc_net_decnet_route);
+#endif
+
+}
+
+
diff --git a/net/decnet/dn_neigh.c b/net/decnet/dn_neigh.c
new file mode 100644
index 000000000..3ca6535be
--- /dev/null
+++ b/net/decnet/dn_neigh.c
@@ -0,0 +1,633 @@
+/*
+ * 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).
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/net.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 <asm/atomic.h>
+#include <asm/spinlock.h>
+#include <net/neighbour.h>
+#include <net/dst.h>
+#include <net/dn.h>
+#include <net/dn_dev.h>
+#include <net/dn_neigh.h>
+#include <net/dn_route.h>
+
+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 = {
+ AF_DECnet,
+ NULL,
+ NULL,
+ dn_long_error_report,
+ dn_long_output,
+ dn_long_output,
+ dev_queue_xmit,
+ dev_queue_xmit
+};
+
+/*
+ * For talking to pointopoint and multidrop devices: DDCMP and X.25
+ */
+static struct neigh_ops dn_short_ops = {
+ AF_DECnet,
+ NULL,
+ NULL,
+ dn_short_error_report,
+ dn_short_output,
+ dn_short_output,
+ dev_queue_xmit,
+ dev_queue_xmit
+};
+
+/*
+ * For talking to DECnet phase III nodes
+ */
+static struct neigh_ops dn_phase3_ops = {
+ AF_DECnet,
+ NULL,
+ NULL,
+ dn_short_error_report, /* Can use short version here */
+ dn_phase3_output,
+ dn_phase3_output,
+ dev_queue_xmit,
+ dev_queue_xmit
+};
+
+struct neigh_table dn_neigh_table = {
+ NULL,
+ PF_DECnet,
+ sizeof(struct dn_neigh),
+ ETH_ALEN,
+ dn_neigh_construct,
+ NULL, /* pconstructor */
+ NULL, /* pdestructor */
+ NULL, /* proxyredo */
+ {
+ NULL,
+ NULL,
+ &dn_neigh_table,
+ 0,
+ NULL,
+ NULL,
+ 30 * HZ, /* base_reachable_time */
+ 1 * HZ, /* retrans_time */
+ 60 * HZ, /* gc_staletime */
+ 30 * HZ, /* reachable_time */
+ 5 * HZ, /* delay_probe_time */
+ 3, /* queue_len */
+ 0, /* ucast_probes */
+ 0, /* app_probes */
+ 0, /* mcast_probes */
+ 0, /* anycast_delay */
+ 0, /* proxy_delay */
+ 0, /* proxy_qlen */
+ 1 * HZ, /* locktime */
+ },
+ 30 * HZ, /* gc_interval */
+ 128, /* gc_thresh1 */
+ 512, /* gc_thresh2 */
+ 1024, /* gc_thresh3 */
+
+};
+
+static int dn_neigh_construct(struct neighbour *neigh)
+{
+ struct device *dev = neigh->dev;
+ struct dn_neigh *dn = (struct dn_neigh *)neigh;
+ struct dn_dev *dn_db = (struct dn_dev *)dev->dn_ptr;
+
+ if (dn_db == NULL)
+ return -EINVAL;
+
+ if (dn_db->neigh_parms)
+ neigh->parms = dn_db->neigh_parms;
+
+ 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;
+
+ dn->blksize = 230;
+
+ return 0;
+}
+
+static void dn_long_error_report(struct neighbour *neigh, struct sk_buff *skb)
+{
+ struct dn_skb_cb *cb = (struct dn_skb_cb *)skb->cb;
+ unsigned char *ptr;
+
+ printk(KERN_DEBUG "dn_long_error_report: called\n");
+
+ if (!(cb->rt_flags & DN_RT_F_RQR)) {
+ kfree_skb(skb);
+ return;
+ }
+
+ skb_push(skb, skb->data - skb->nh.raw);
+ ptr = skb->data;
+
+ *(unsigned short *)ptr = dn_htons(skb->len - 2);
+ ptr += 2;
+
+ 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;
+ dn_dn2eth(ptr, dn_ntohs(cb->src));
+ ptr += 8;
+ dn_dn2eth(ptr, dn_ntohs(cb->dst));
+ ptr += 6;
+ *ptr = 0;
+
+ skb->dst->neighbour->ops->queue_xmit(skb);
+}
+
+
+static void dn_short_error_report(struct neighbour *neigh, struct sk_buff *skb)
+{
+ struct dn_skb_cb *cb = (struct dn_skb_cb *)skb->cb;
+ unsigned char *ptr;
+
+ printk(KERN_DEBUG "dn_short_error_report: called\n");
+
+ if (!(cb->rt_flags & DN_RT_F_RQR)) {
+ kfree_skb(skb);
+ return;
+ }
+
+ skb_push(skb, skb->data - skb->nh.raw);
+ ptr = skb->data;
+
+ *(unsigned short *)ptr = dn_htons(skb->len - 2);
+ ptr += 2;
+ *ptr++ = (cb->rt_flags & ~DN_RT_F_RQR) | DN_RT_F_RTS;
+
+ *(dn_address *)ptr = cb->src;
+ ptr += 2;
+ *(dn_address *)ptr = cb->dst;
+ ptr += 2;
+ *ptr = 0;
+
+ skb->dst->neighbour->ops->queue_xmit(skb);
+}
+
+
+static int dn_long_output(struct sk_buff *skb)
+{
+ struct dst_entry *dst = skb->dst;
+ struct neighbour *neigh = dst->neighbour;
+ struct device *dev = neigh->dev;
+ struct dn_dev *dn_db = dev->dn_ptr;
+ 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 = (struct dn_skb_cb *)skb->cb;
+
+
+ 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, cb->dst);
+ lp->s_area = lp->s_subarea = 0;
+ dn_dn2eth(lp->s_id, cb->src);
+ lp->nl2 = 0;
+ lp->visit_ct = cb->hops & 0x3f;
+ lp->s_class = 0;
+ lp->pt = 0;
+
+ skb->nh.raw = skb->data;
+
+ if (dev->hard_header(skb, dev, ntohs(skb->protocol), neigh->ha,
+ dn_db->addr, skb->len) >= 0)
+ return neigh->ops->queue_xmit(skb);
+
+ if (net_ratelimit())
+ printk(KERN_DEBUG "dn_long_output: oops, can't sent packet\n");
+
+ kfree_skb(skb);
+ return -EINVAL;
+}
+
+static int dn_short_output(struct sk_buff *skb)
+{
+ struct dst_entry *dst = skb->dst;
+ struct neighbour *neigh = dst->neighbour;
+ struct 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 = (struct dn_skb_cb *)skb->cb;
+
+
+ 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;
+
+ if (dev->hard_header(skb, dev, ntohs(skb->protocol), neigh->ha,
+ NULL, skb->len) >= 0)
+ return neigh->ops->queue_xmit(skb);
+
+ kfree_skb(skb);
+ return -EINVAL;
+}
+
+/*
+ * 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 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 = (struct dn_skb_cb *)skb->cb;
+
+ 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 & __constant_htons(0x03ff);
+ sp->srcnode = cb->src & __constant_htons(0x03ff);
+ sp->forward = cb->hops & 0x3f;
+
+ skb->nh.raw = skb->data;
+
+ if (dev->hard_header(skb, dev, ntohs(skb->protocol), neigh->ha,
+ NULL, skb->len) >= 0)
+ return neigh->ops->queue_xmit(skb);
+
+ kfree_skb(skb);
+ return -EINVAL;
+}
+
+/*
+ * 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
+ */
+struct neighbour *dn_neigh_lookup(struct neigh_table *tbl, void *ptr)
+{
+ int i;
+ struct neighbour *neigh;
+
+ start_bh_atomic();
+ for(i = 0; i < NEIGH_HASHMASK; i++) {
+ for(neigh = tbl->hash_buckets[i]; neigh != NULL; neigh = neigh->next) {
+ if (memcmp(neigh->primary_key, ptr, ETH_ALEN) == 0) {
+ atomic_inc(&neigh->refcnt);
+ end_bh_atomic();
+ return neigh;
+ }
+ }
+ }
+ end_bh_atomic();
+
+ return NULL;
+}
+
+
+/*
+ * Any traffic on a pointopoint link causes the timer to be reset
+ * for the entry in the neighbour table.
+ */
+void dn_neigh_pointopoint_notify(struct sk_buff *skb)
+{
+ return;
+}
+
+/*
+ * Pointopoint link receives a hello message
+ */
+void dn_neigh_pointopoint_hello(struct sk_buff *skb)
+{
+ kfree_skb(skb);
+}
+
+/*
+ * Ethernet router hello message received
+ */
+void 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;
+
+ start_bh_atomic();
+ neigh = __neigh_lookup(&dn_neigh_table, msg->id, skb->dev, 1);
+ end_bh_atomic();
+
+ dn = (struct dn_neigh *)neigh;
+
+ if (neigh) {
+ neigh_update(neigh, msg->id, NUD_NOARP, 1, 0);
+ neigh->used = jiffies;
+
+ dn_db = (struct dn_dev *)neigh->dev->dn_ptr;
+
+ 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;
+ 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)));
+ }
+
+ neigh_release(neigh);
+ }
+
+ kfree_skb(skb);
+}
+
+/*
+ * Endnode hello message received
+ */
+void 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;
+
+ start_bh_atomic();
+ neigh = __neigh_lookup(&dn_neigh_table, msg->id, skb->dev, 1);
+ end_bh_atomic();
+
+ dn = (struct dn_neigh *)neigh;
+
+ if (neigh) {
+ neigh_update(neigh, msg->id, NUD_NOARP, 1, 0);
+ neigh->used = jiffies;
+
+ dn->flags &= ~(DN_NDFLAG_R1 | DN_NDFLAG_R2);
+ dn->blksize = dn_ntohs(msg->blksize);
+ dn->priority = 0;
+
+ neigh_release(neigh);
+ }
+
+ kfree_skb(skb);
+}
+
+
+#ifdef CONFIG_DECNET_ROUTER
+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;
+}
+
+int dn_neigh_elist(struct device *dev, unsigned char *ptr, int n)
+{
+ int t = 0;
+ int i;
+ struct neighbour *neigh;
+ struct dn_neigh *dn;
+ struct neigh_table *tbl = &dn_neigh_table;
+ unsigned char *rs = ptr;
+
+ start_bh_atomic();
+
+ for(i = 0; i < NEIGH_HASHMASK; i++) {
+ for(neigh = tbl->hash_buckets[i]; neigh != NULL; neigh = neigh->next) {
+ if (neigh->dev != dev)
+ continue;
+ dn = (struct dn_neigh *)neigh;
+ if (!(dn->flags & (DN_NDFLAG_R1|DN_NDFLAG_R2)))
+ continue;
+ if (decnet_node_type == DN_RT_INFO_L1RT && (dn->flags & DN_NDFLAG_R2))
+ continue;
+ if (t == n)
+ rs = dn_find_slot(ptr, n, dn->priority);
+ else
+ t++;
+ if (rs == NULL)
+ continue;
+ memcpy(rs, dn->addr, ETH_ALEN);
+ rs += 6;
+ *rs = neigh->nud_state & NUD_CONNECTED ? 0x80 : 0x0;
+ *rs |= dn->priority;
+ rs++;
+ }
+ }
+
+ end_bh_atomic();
+
+ return t;
+}
+#endif /* CONFIG_DECNET_ROUTER */
+
+
+
+#ifdef CONFIG_PROC_FS
+int dn_neigh_get_info(char *buffer, char **start, off_t offset, int length, int dummy)
+{
+ int len = 0;
+ off_t pos = 0;
+ off_t begin = 0;
+ struct neighbour *n;
+ int i;
+ char buf[DN_ASCBUF_LEN];
+
+ len += sprintf(buffer + len, "Addr Flags State Use Blksize Dev\n");
+
+ for(i=0;i <= NEIGH_HASHMASK; i++) {
+ read_lock_bh(&dn_neigh_table.lock);
+ n = dn_neigh_table.hash_buckets[i];
+ for(; n != NULL; n = n->next) {
+ struct dn_neigh *dn = (struct dn_neigh *)n;
+
+ read_lock(&n->lock);
+ len += sprintf(buffer+len, "%-7s %s%s%s %02x %02d %07ld %-8s\n",
+ dn_addr2asc(dn_ntohs(dn_eth2dn(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);
+
+ pos = begin + len;
+
+ if (pos < offset) {
+ len = 0;
+ begin = pos;
+ }
+
+ if (pos > offset + length) {
+ read_unlock_bh(&dn_neigh_table.lock);
+ goto done;
+ }
+ }
+ read_unlock_bh(&dn_neigh_table.lock);
+ }
+
+done:
+
+ *start = buffer + (offset - begin);
+ len -= offset - begin;
+
+ if (len > length) len = length;
+
+ return len;
+}
+
+static struct proc_dir_entry proc_net_dn_neigh = {
+ PROC_NET_DN_ADJ, 12, "decnet_neigh",
+ S_IFREG | S_IRUGO, 1, 0, 0,
+ 0, &proc_net_inode_operations,
+ dn_neigh_get_info
+};
+
+#endif
+
+void __init dn_neigh_init(void)
+{
+ neigh_table_init(&dn_neigh_table);
+
+#ifdef CONFIG_PROC_FS
+ proc_net_register(&proc_net_dn_neigh);
+#endif /* CONFIG_PROC_FS */
+}
+
+#ifdef CONFIG_DECNET_MODULE
+void dn_neigh_cleanup(void)
+{
+#ifdef CONFIG_PROC_FS
+ proc_net_unregister(PROC_NET_DN_ADJ);
+#endif /* CONFIG_PROC_FS */
+ neigh_table_clear(&dn_neigh_table);
+}
+#endif /* CONFIG_DECNET_MODULE */
diff --git a/net/decnet/dn_nsp_in.c b/net/decnet/dn_nsp_in.c
new file mode 100644
index 000000000..6d10df045
--- /dev/null
+++ b/net/decnet/dn_nsp_in.c
@@ -0,0 +1,703 @@
+
+/*
+ * 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.
+ */
+
+/******************************************************************************
+ (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 <asm/segment.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 <net/neighbour.h>
+#include <net/dst.h>
+#include <net/dn_nsp.h>
+#include <net/dn_dev.h>
+#include <net/dn_route.h>
+#include <net/dn_raw.h>
+
+
+/*
+ * 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 = &sk->protinfo.dn;
+ unsigned short type = ((ack >> 12) & 0x0003);
+ int wakeup = 0;
+
+ /* printk(KERN_DEBUG "dn_ack: %hd 0x%04hx\n", type, ack); */
+
+ switch(type) {
+ case 0: /* ACK - Data */
+ if (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 (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 && !sk->dead)
+ 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;
+}
+
+
+/*
+ * This function uses a slightly different lookup method
+ * to find its sockets, since it searches on object name/number
+ * rather than port numbers
+ */
+static int dn_conninit_rx(struct sk_buff *skb)
+{
+ struct sock *sk;
+ struct dn_skb_cb *cb = (struct dn_skb_cb *)skb->cb;
+ struct nsp_conn_init_msg *msg = (struct nsp_conn_init_msg *)skb->data;
+ struct sockaddr_dn addr;
+ unsigned char type = 0;
+
+ memset(&addr, 0, sizeof(struct sockaddr_dn));
+
+ cb->src_port = msg->srcaddr;
+ cb->dst_port = msg->dstaddr;
+ cb->services = msg->services;
+ cb->info = msg->info;
+ cb->segsize = dn_ntohs(msg->segsize);
+
+ skb_pull(skb, sizeof(*msg));
+
+ /* printk(KERN_DEBUG "username2sockaddr 1\n"); */
+ if (dn_username2sockaddr(skb->data, skb->len, &addr, &type) < 0)
+ goto free_out;
+
+ if (type > 1)
+ goto free_out;
+
+ /* printk(KERN_DEBUG "looking for listener...\n"); */
+ if ((sk = dn_sklist_find_listener(&addr)) == NULL)
+ return 1;
+
+ /* printk(KERN_DEBUG "checking backlog...\n"); */
+ if (sk->ack_backlog >= sk->max_ack_backlog)
+ goto free_out;
+
+ /* printk(KERN_DEBUG "waking up socket...\n"); */
+ sk->ack_backlog++;
+ skb_queue_tail(&sk->receive_queue, skb);
+ sk->state_change(sk);
+
+ return 0;
+
+free_out:
+ kfree_skb(skb);
+ return 0;
+}
+
+static void dn_nsp_conn_conf(struct sock *sk, struct sk_buff *skb)
+{
+ struct dn_skb_cb *cb = (struct dn_skb_cb *)skb->cb;
+ struct dn_scp *scp = &sk->protinfo.dn;
+
+ if (skb->len < 3)
+ goto out;
+
+ cb->services = *skb->data;
+ cb->info = *(skb->data+1);
+ skb_pull(skb, 2);
+ cb->segsize = dn_ntohs(*(__u16 *)skb->data);
+ skb_pull(skb, 2);
+
+ /*
+ * FIXME: Check out services and info fields to check that
+ * we can talk to this kind of node.
+ */
+
+ if ((scp->state == DN_CI) || (scp->state == DN_CD)) {
+ scp->persist = 0;
+ scp->addrrem = cb->src_port;
+ sk->state = TCP_ESTABLISHED;
+ scp->state = DN_RUN;
+
+ if (scp->mss > cb->segsize)
+ scp->mss = cb->segsize;
+ if (scp->mss < 230)
+ scp->mss = 230;
+
+ 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_lnk(sk, DN_NOCHANGE);
+ if (!sk->dead)
+ 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 = &sk->protinfo.dn;
+
+ 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 = &sk->protinfo.dn;
+ struct dn_skb_cb *cb = (struct dn_skb_cb *)skb->cb;
+ unsigned short reason;
+
+ /* printk(KERN_DEBUG "DECnet: discinit %d\n", skb->len); */
+
+ 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->state = TCP_CLOSE;
+
+ /* printk(KERN_DEBUG "DECnet: discinit\n"); */
+
+ switch(scp->state) {
+ case DN_CI:
+ case DN_CD:
+ scp->state = DN_RJ;
+ break;
+ case DN_RUN:
+ sk->shutdown |= SHUTDOWN_MASK;
+ scp->state = DN_DN;
+ break;
+ case DN_DI:
+ scp->state = DN_DIC;
+ break;
+ }
+
+ if (!sk->dead)
+ sk->state_change(sk);
+
+ dn_destroy_sock(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 = &sk->protinfo.dn;
+ unsigned short reason;
+
+ if (skb->len != 2)
+ goto out;
+
+ reason = dn_ntohs(*(__u16 *)skb->data);
+
+ 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->shutdown |= SHUTDOWN_MASK;
+ case DN_CC:
+ scp->state = DN_CN;
+ }
+
+ if (!sk->dead)
+ sk->state_change(sk);
+
+ dn_destroy_sock(sk);
+
+out:
+ kfree_skb(skb);
+}
+
+static void dn_nsp_linkservice(struct sock *sk, struct sk_buff *skb)
+{
+ struct dn_skb_cb *cb = (struct dn_skb_cb *)skb->cb;
+ unsigned short segnum;
+ unsigned char lsflags;
+ char fcval;
+
+ if (skb->len != 4)
+ goto out;
+
+ cb->segnum = segnum = dn_ntohs(*(__u16 *)skb->data);
+ skb_pull(skb, 2);
+ lsflags = *(unsigned char *)skb->data;
+ skb_pull(skb, 1);
+ fcval = *(char *)skb->data;
+
+ if (lsflags & 0xf0)
+ goto out;
+
+ if (((sk->protinfo.dn.numoth_rcv + 1) & 0x0FFF) == (segnum & 0x0FFF)) {
+ sk->protinfo.dn.numoth_rcv += 1;
+ switch(lsflags & 0x03) {
+ case 0x00:
+ break;
+ case 0x01:
+ sk->protinfo.dn.flowrem_sw = DN_DONTSEND;
+ break;
+ case 0x02:
+ sk->protinfo.dn.flowrem_sw = DN_SEND;
+ dn_nsp_output(sk);
+ if (!sk->dead)
+ sk->state_change(sk);
+ }
+
+ }
+
+ dn_nsp_send_oth_ack(sk);
+
+out:
+ kfree_skb(skb);
+}
+
+/*
+ * Copy of sock_queue_rcv_skb (from net/core/datagram.c) to
+ * queue other data segments. Also we send SIGURG here instead
+ * of the normal SIGIO, 'cos its out of band data.
+ */
+static __inline__ int dn_queue_other_skb(struct sock *sk, struct sk_buff *skb)
+{
+ struct dn_scp *scp = &sk->protinfo.dn;
+
+ /* Cast skb->rcvbuf to unsigned... It's pointless, but reduces
+ number of warnings when compiling with -W --ANK
+ */
+ if (atomic_read(&sk->rmem_alloc) + skb->truesize >= (unsigned)sk->rcvbuf
+)
+ return -ENOMEM;
+
+#ifdef CONFIG_FILTER
+ if (sk->filter)
+ {
+ if (sk_filter(skb, sk->filter))
+ return -EPERM; /* Toss packet */
+ }
+#endif /* CONFIG_FILTER */
+
+ skb_set_owner_r(skb, sk);
+ skb_queue_tail(&scp->other_receive_queue, skb);
+
+ if (!sk->dead) {
+ struct socket *sock = sk->socket;
+ wake_up_interruptible(sk->sleep);
+ if (!(sock->flags & SO_WAITDATA) && sock->fasync_list)
+ kill_fasync(sock->fasync_list, SIGURG);
+ }
+
+ return 0;
+}
+
+static void dn_nsp_otherdata(struct sock *sk, struct sk_buff *skb)
+{
+ struct dn_scp *scp = &sk->protinfo.dn;
+ unsigned short segnum;
+ struct dn_skb_cb *cb = (struct dn_skb_cb *)skb->cb;
+ int queued = 0;
+
+ if (skb->len < 2)
+ goto out;
+
+ cb->segnum = segnum = dn_ntohs(*(__u16 *)skb->data);
+ skb_pull(skb, 2);
+
+ if (((sk->protinfo.dn.numoth_rcv + 1) & 0x0fff) == (segnum & 0x0fff)) {
+
+ if (dn_queue_other_skb(sk, skb) == 0) {
+ sk->protinfo.dn.numoth_rcv++;
+ 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 = (struct dn_skb_cb *)skb->cb;
+ struct dn_scp *scp = &sk->protinfo.dn;
+
+ if (skb->len < 2)
+ goto out;
+
+ cb->segnum = segnum = dn_ntohs(*(__u16 *)skb->data);
+ skb_pull(skb, 2);
+
+ if (((sk->protinfo.dn.numdat_rcv + 1) & 0x0FFF) ==
+ (segnum & 0x0FFF)) {
+
+ if (sock_queue_rcv_skb(sk, skb) == 0) {
+ sk->protinfo.dn.numdat_rcv++;
+ queued = 1;
+ }
+
+ if ((scp->flowloc_sw == DN_SEND) && dn_congested(sk)) {
+ scp->flowloc_sw = DN_DONTSEND;
+ dn_nsp_send_lnk(sk, DN_DONTSEND);
+ }
+ }
+
+ 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_conninit(struct sk_buff *skb)
+{
+ struct dn_skb_cb *cb = (struct dn_skb_cb *)skb->cb;
+ struct sock *sk;
+
+ cb->dst_port = cb->src_port;
+ cb->src_port = 0;
+
+ if ((sk = dn_find_by_skb(skb)) != NULL) {
+ struct dn_scp *scp = &sk->protinfo.dn;
+ if (scp->state == DN_CI) {
+ scp->state = DN_NC;
+ sk->state = TCP_CLOSE;
+ if (!sk->dead)
+ sk->state_change(sk);
+ }
+ }
+
+ kfree_skb(skb);
+}
+
+int dn_nsp_rx(struct sk_buff *skb)
+{
+ struct dn_skb_cb *cb = (struct dn_skb_cb *)skb->cb;
+ struct sock *sk = NULL;
+ unsigned char *ptr = (unsigned char *)skb->data;
+
+ skb->h.raw = skb->data;
+ cb->nsp_flags = *ptr++;
+
+ if (decnet_debug_level & 1)
+ printk(KERN_DEBUG "dn_nsp_rx: Message type 0x%02x\n", (int)cb->nsp_flags);
+
+#ifdef CONFIG_DECNET_RAW
+ dn_raw_rx_nsp(skb);
+#endif /* CONFIG_DECNET_RAW */
+
+ if (skb->len < 2)
+ goto free_out;
+
+ if (cb->nsp_flags & 0x83)
+ goto free_out;
+
+ /*
+ * Returned packets...
+ */
+ if (cb->rt_flags & DN_RT_F_RTS) {
+ if ((cb->nsp_flags & 0x0c) == 0x08) {
+ switch(cb->nsp_flags & 0x70) {
+ case 0x10:
+ case 0x60:
+ dn_returned_conninit(skb);
+ goto out;
+ }
+ }
+ 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:
+ return dn_conninit_rx(skb);
+ }
+ }
+
+ if (skb->len < 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 (skb->len >= 5) {
+ cb->src_port = *(unsigned short *)ptr;
+ ptr += 2;
+ skb_pull(skb, 5);
+ }
+
+ /*
+ * Find the socket to which this skb is destined.
+ */
+ if ((sk = dn_find_by_skb(skb)) != NULL) {
+ struct dn_scp *scp = &sk->protinfo.dn;
+ int ret;
+ /* printk(KERN_DEBUG "dn_nsp_rx: Found a socket\n"); */
+
+ /* Reset backoff */
+ scp->nsp_rxtshift = 0;
+
+ bh_lock_sock(sk);
+ ret = 0;
+ if (sk->lock.users == 0)
+ ret = dn_nsp_backlog_rcv(sk, skb);
+ else
+ sk_add_backlog(sk, skb);
+ bh_unlock_sock(sk);
+
+ return ret;
+ }
+ return 1;
+
+free_out:
+ kfree_skb(skb);
+out:
+ return 0;
+}
+
+/*
+ * 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 = &sk->protinfo.dn;
+ struct dn_skb_cb *cb = (struct dn_skb_cb *)skb->cb;
+
+ /*
+ * Control packet.
+ */
+ if ((cb->nsp_flags & 0x0c) == 0x08) {
+ /* printk(KERN_DEBUG "control type\n"); */
+ switch(cb->nsp_flags & 0x70) {
+ 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;
+
+ 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. All
+ * data frames can also kick a CC socket into RUN.
+ */
+ if ((cb->nsp_flags & 0x0c) == 0) {
+
+ if ((scp->state == DN_CC) && !sk->dead) {
+ scp->state = DN_RUN;
+ sk->state = TCP_ESTABLISHED;
+ sk->state_change(sk);
+ }
+
+ 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 0;
+}
+
diff --git a/net/decnet/dn_nsp_out.c b/net/decnet/dn_nsp_out.c
new file mode 100644
index 000000000..b4211e91b
--- /dev/null
+++ b/net/decnet/dn_nsp_out.c
@@ -0,0 +1,640 @@
+
+/*
+ * 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
+ */
+
+/******************************************************************************
+ (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 <asm/segment.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/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 };
+
+/*
+ * 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 dn_send_skb() function will
+ * recognise skbs on the same basis.
+ *
+ * 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->sk = sk;
+ skb->pkt_type = PACKET_OUTGOING;
+
+ 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, int *size, int noblock, int *err)
+{
+ int space;
+ int len;
+ struct sk_buff *skb = NULL;
+
+ *err = 0;
+
+ while(skb == NULL) {
+ if (signal_pending(current)) {
+ *err = ERESTARTSYS;
+ break;
+ }
+
+ if (sk->shutdown & SEND_SHUTDOWN) {
+ *err = EINVAL;
+ break;
+ }
+
+ if (sk->err)
+ break;
+
+ len = *size + 11;
+ space = sk->sndbuf - atomic_read(&sk->wmem_alloc);
+
+ if (space < len) {
+ if ((sk->socket->type == SOCK_STREAM) && (space >= (16 + 11)))
+ len = space;
+ }
+
+ if (space < len) {
+ sk->socket->flags |= SO_NOSPACE;
+ if (noblock) {
+ *err = EWOULDBLOCK;
+ break;
+ }
+
+ sk->socket->flags &= ~SO_NOSPACE;
+ SOCK_SLEEP_PRE(sk)
+
+ if ((sk->sndbuf - atomic_read(&sk->wmem_alloc)) < len)
+ schedule();
+
+ SOCK_SLEEP_POST(sk)
+ continue;
+ }
+
+ if ((skb = dn_alloc_skb(sk, len, GFP_KERNEL)) == NULL)
+ continue;
+
+ skb->destructor = sock_wfree;
+ atomic_add(skb->truesize, &sk->wmem_alloc);
+
+ *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 = &sk->protinfo.dn;
+
+ 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 = &sk->protinfo.dn;
+ 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); */
+}
+
+/*
+ * Walk the queues, otherdata/linkservice first. Send as many
+ * frames as the window allows, increment send counts on all
+ * skbs which are sent. Reduce the window if we are retransmitting
+ * frames.
+ */
+void dn_nsp_output(struct sock *sk)
+{
+ struct dn_scp *scp = &sk->protinfo.dn;
+ unsigned long win = scp->snd_window;
+ struct sk_buff *skb, *skb2, *list;
+ struct dn_skb_cb *cb;
+ int reduce_win = 0;
+
+ /* printk(KERN_DEBUG "dn_nsp_output: ping\n"); */
+
+ /*
+ * First we check for otherdata/linkservice messages
+ */
+ skb = scp->other_xmit_queue.next;
+ list = (struct sk_buff *)&scp->other_xmit_queue;
+ while(win && (skb != list)) {
+ if ((skb2 = skb_clone(skb, GFP_ATOMIC)) != NULL) {
+ cb = (struct dn_skb_cb *)skb;
+ if (cb->xmit_count > 0)
+ reduce_win = 1;
+ else
+ cb->stamp = jiffies;
+ cb->xmit_count++;
+ skb2->sk = sk;
+ dn_send_skb(skb2);
+ }
+ skb = skb->next;
+ win--;
+ }
+
+ /*
+ * If we may not send any data, we don't.
+ * Should this apply to otherdata as well ? - SJW
+ */
+ if (scp->flowrem_sw != DN_SEND)
+ goto recalc_window;
+
+ skb = scp->data_xmit_queue.next;
+ list = (struct sk_buff *)&scp->data_xmit_queue;
+ while(win && (skb != list)) {
+ if ((skb2 = skb_clone(skb, GFP_ATOMIC)) != NULL) {
+ cb = (struct dn_skb_cb *)skb;
+ if (cb->xmit_count > 0)
+ reduce_win = 1;
+ else
+ cb->stamp = jiffies;
+ cb->xmit_count++;
+ skb2->sk = sk;
+ dn_send_skb(skb2);
+ }
+ skb = skb->next;
+ win--;
+ }
+
+ /*
+ * 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) {
+ /* printk(KERN_DEBUG "Window reduction %ld\n", scp->snd_window); */
+ 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 = &sk->protinfo.dn;
+
+ 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;
+}
+
+void dn_nsp_queue_xmit(struct sock *sk, struct sk_buff *skb, int oth)
+{
+ struct dn_scp *scp = &sk->protinfo.dn;
+ struct dn_skb_cb *cb = (struct dn_skb_cb *)skb->cb;
+ unsigned long t = ((scp->nsp_srtt >> 2) + scp->nsp_rttvar) >> 1;
+ struct sk_buff *skb2;
+
+ if (t < HZ) t = HZ;
+ /*
+ * 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;
+
+ /* printk(KERN_DEBUG "Window: %lu\n", scp->snd_window); */
+
+ cb->xmit_count = 0;
+
+ 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;
+
+ if ((skb2 = skb_clone(skb, GFP_ATOMIC)) != NULL) {
+ cb->stamp = jiffies;
+ cb->xmit_count++;
+ skb2->sk = sk;
+ dn_send_skb(skb2);
+ }
+}
+
+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 = (struct dn_skb_cb *)skb->cb;
+ struct dn_scp *scp = &sk->protinfo.dn;
+ struct sk_buff *skb2, *list, *ack = NULL;
+ int wakeup = 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 = (struct dn_skb_cb *)skb2->cb;
+
+ if (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); */
+
+ wakeup = 1;
+ pkttime = cb2->stamp;
+ xmit_count = cb2->xmit_count;
+ segnum = cb2->segnum;
+ skb_unlink(ack);
+ kfree_skb(ack);
+ ack = NULL;
+ if (xmit_count == 1) {
+ if (equal(segnum, acknum))
+ dn_nsp_rtt(sk, (long)(pkttime - reftime));
+
+ if (scp->snd_window < NSP_MAX_WINDOW)
+ scp->snd_window++;
+ }
+ }
+
+#if 0 /* Turned off due to possible interference in socket shutdown */
+ if ((skb_queue_len(&scp->data_xmit_queue) == 0) &&
+ (skb_queue_len(&scp->other_xmit_queue) == 0))
+ scp->persist = 0;
+#endif
+
+ return wakeup;
+}
+
+void dn_nsp_send_data_ack(struct sock *sk)
+{
+ struct sk_buff *skb = NULL;
+ struct nsp_data_ack_msg *msg;
+
+ if ((skb = dn_alloc_skb(sk, 200, GFP_ATOMIC)) == NULL)
+ return;
+
+ msg = (struct nsp_data_ack_msg *)skb_put(skb,sizeof(*msg));
+
+ msg->msgflg = 0x04; /* data ack message */
+ msg->dstaddr = sk->protinfo.dn.addrrem;
+ msg->srcaddr = sk->protinfo.dn.addrloc;
+ msg->acknum = dn_htons((sk->protinfo.dn.numdat_rcv & 0x0FFF) | 0x8000);
+
+ sk->protinfo.dn.ackxmt_dat = sk->protinfo.dn.numdat_rcv;
+
+ dn_send_skb(skb);
+}
+
+void dn_nsp_send_oth_ack(struct sock *sk)
+{
+ struct sk_buff *skb = NULL;
+ struct nsp_data_ack_msg *msg;
+
+ if ((skb = dn_alloc_skb(sk, 200, GFP_ATOMIC)) == NULL)
+ return;
+
+ /* printk(KERN_DEBUG "dn_send_oth_ack\n"); */
+
+ msg = (struct nsp_data_ack_msg *)skb_put(skb,sizeof(*msg));
+
+ msg->msgflg = 0x14; /* oth ack message */
+ msg->dstaddr = sk->protinfo.dn.addrrem;
+ msg->srcaddr = sk->protinfo.dn.addrloc;
+ msg->acknum = dn_htons((sk->protinfo.dn.numoth_rcv & 0x0FFF) | 0x8000);
+
+ sk->protinfo.dn.ackxmt_oth = sk->protinfo.dn.numoth_rcv;
+
+ dn_send_skb(skb);
+}
+
+
+void dn_send_conn_ack (struct sock *sk)
+{
+ struct dn_scp *scp = &sk->protinfo.dn;
+ struct sk_buff *skb = NULL;
+ struct nsp_conn_ack_msg *msg;
+
+ if ((skb = dn_alloc_skb(sk, 3, GFP_KERNEL)) == NULL)
+ return;
+
+ msg = (struct nsp_conn_ack_msg *)skb_put(skb, 3);
+ msg->msgflg = 0x24;
+ msg->dstaddr = scp->addrrem;
+
+ dn_send_skb(skb);
+}
+
+void dn_nsp_delayed_ack(struct sock *sk)
+{
+ struct dn_scp *scp = &sk->protinfo.dn;
+
+ 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);
+}
+
+void dn_send_conn_conf (struct sock *sk)
+{
+ struct dn_scp *scp = &sk->protinfo.dn;
+ struct sk_buff *skb = NULL;
+ struct nsp_conn_init_msg *msg;
+ unsigned short int aux;
+
+ if ((skb = dn_alloc_skb(sk, 50 + scp->conndata_out.opt_optl, GFP_KERNEL)) == 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 = 0x01;
+ msg->info = 0x03;
+ msg->segsize = dn_htons(0x05B3);
+
+ if (scp->conndata_out.opt_optl > 0) {
+ aux = scp->conndata_out.opt_optl;
+ *skb_put(skb,1) = aux;
+ memcpy(skb_put(skb, aux), scp->conndata_out.opt_data, aux);
+ }
+
+
+ dn_send_skb(skb);
+}
+
+void dn_send_disc (struct sock *sk, unsigned char msgflg, unsigned short reason)
+{
+ struct dn_scp *scp = &sk->protinfo.dn;
+ struct sk_buff *skb = NULL;
+ int ddl = (msgflg == NSP_DISCINIT || msgflg == 0x38) ? (1 + scp->discdata_out.opt_optl) : 0;
+ int size = 7 + ddl;
+ unsigned char *msg;
+
+ if ((skb = dn_alloc_skb(sk, size, GFP_ATOMIC)) == NULL)
+ return;
+
+ if (reason == 0) reason = scp->discdata_out.opt_status;
+
+ msg = skb_put(skb, size);
+ *msg++ = msgflg;
+ *(__u16 *)msg = scp->addrrem;
+ msg += 2;
+ *(__u16 *)msg = scp->addrloc;
+ msg += 2;
+ *(__u16 *)msg = dn_htons(reason);
+ msg += 2;
+
+ if (ddl) {
+ *msg++ = scp->discdata_out.opt_optl;
+ memcpy(msg, scp->discdata_out.opt_data, scp->discdata_out.opt_optl);
+ }
+
+ dn_send_skb(skb);
+}
+
+void dn_nsp_send_lnk(struct sock *sk, unsigned short flgs)
+{
+ struct dn_scp *scp = &sk->protinfo.dn;
+ struct sk_buff *skb = NULL;
+ struct nsp_data_seg_msg *msg;
+ struct nsp_data_opt_msg *msg1;
+ struct dn_skb_cb *cb;
+
+ if ((skb = dn_alloc_skb(sk, 80, GFP_ATOMIC)) == NULL)
+ return;
+
+ cb = (struct dn_skb_cb *)skb->cb;
+ msg = (struct nsp_data_seg_msg *)skb_put(skb, sizeof(*msg));
+ msg->msgflg = 0x10; /* Link svc message */
+ msg->dstaddr = scp->addrrem;
+ msg->srcaddr = scp->addrloc;
+
+ msg1 = (struct nsp_data_opt_msg *)skb_put(skb, sizeof(*msg1));
+ msg1->acknum = dn_htons((scp->ackxmt_oth & 0x0FFF) | 0x8000);
+ msg1->segnum = dn_htons(cb->segnum = (scp->numoth++ & 0x0FFF));
+ msg1->lsflgs = flgs;
+
+ /* printk(KERN_DEBUG "dn_nsp_send_lnk: %02x\n", flgs); */
+
+ dn_nsp_queue_xmit(sk, skb, 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 = &sk->protinfo.dn;
+
+ if (scp->state == DN_CI) {
+ dn_nsp_send_conninit(sk, NSP_RCI);
+ scp->persist = dn_nsp_persist(sk);
+ }
+
+ return 0;
+}
+
+void dn_nsp_send_conninit(struct sock *sk, unsigned char msgflg)
+{
+ struct dn_scp *scp = &sk->protinfo.dn;
+ struct sk_buff *skb = NULL;
+ struct nsp_conn_init_msg *msg;
+ unsigned char aux;
+ unsigned char menuver;
+ struct dn_skb_cb *cb;
+ unsigned char type = 1;
+
+ if ((skb = dn_alloc_skb(sk, 200, (msgflg == NSP_CI) ? GFP_KERNEL : GFP_ATOMIC)) == NULL)
+ return;
+
+ cb = (struct dn_skb_cb *)skb->cb;
+ msg = (struct nsp_conn_init_msg *)skb_put(skb,sizeof(*msg));
+
+ msg->msgflg = msgflg;
+ msg->dstaddr = 0x0000; /* Remote Node will assign it*/
+
+ if (msgflg == NSP_CI)
+ sk->protinfo.dn.addrloc = dn_alloc_port();
+
+ msg->srcaddr = sk->protinfo.dn.addrloc;
+ msg->services = 1 | NSP_FC_NONE; /* Requested flow control */
+ msg->info = 0x03; /* Version Number */
+ msg->segsize = dn_htons(1459); /* 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);
+
+ sk->protinfo.dn.persist = dn_nsp_persist(sk);
+ sk->protinfo.dn.persist_fxn = dn_nsp_retrans_conninit;
+
+ cb->rt_flags = DN_RT_F_RQR;
+
+ dn_send_skb(skb);
+}
+
diff --git a/net/decnet/dn_raw.c b/net/decnet/dn_raw.c
new file mode 100644
index 000000000..49cc8c4a9
--- /dev/null
+++ b/net/decnet/dn_raw.c
@@ -0,0 +1,383 @@
+/*
+ * 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 Raw Sockets Interface
+ *
+ * Author: Steve Whitehouse <SteveW@ACM.org>
+ *
+ *
+ * Changes:
+ * Steve Whitehouse - connect() function.
+ */
+
+#include <linux/config.h>
+#include <linux/net.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <net/sock.h>
+#include <net/dn.h>
+#include <net/dn_raw.h>
+
+static struct sock *dn_raw_nsp_sklist = NULL;
+static struct sock *dn_raw_routing_sklist = NULL;
+#ifdef CONFIG_DECNET_MOP
+static struct sock *dn_raw_mop_sklist = NULL;
+#endif /* CONFIG_DECNET_MOP */
+
+static void dn_raw_autobind(struct sock *sk)
+{
+
+ switch(sk->protocol) {
+ case DNPROTO_NSP:
+ sklist_insert_socket(&dn_raw_nsp_sklist, sk);
+ break;
+ case DNPROTO_ROU:
+ sklist_insert_socket(&dn_raw_routing_sklist, sk);
+ break;
+#ifdef CONFIG_DECNET_MOP
+ case DNPROTO_MOP:
+ sklist_insert_socket(&dn_raw_mop_sklist, sk);
+#endif /* CONFIG_DECNET_MOP */
+ default:
+ printk(KERN_DEBUG "dn_raw_autobind: Unknown protocol\n");
+ return;
+ }
+
+ sk->zapped = 0;
+}
+
+static int dn_raw_release(struct socket *sock, struct socket *peer)
+{
+ struct sock *sk = sock->sk;
+
+ if (sk == NULL)
+ return 0;
+
+ if (!sk->dead) sk->state_change(sk);
+
+ sk->dead = 1;
+ sk->socket = NULL;
+ sock->sk = NULL;
+
+ switch(sk->protocol) {
+ case DNPROTO_NSP:
+ sklist_destroy_socket(&dn_raw_nsp_sklist, sk);
+ break;
+ case DNPROTO_ROU:
+ sklist_destroy_socket(&dn_raw_routing_sklist, sk);
+ break;
+#ifdef CONFIG_DECNET_MOP
+ case DNPROTO_MOP:
+ sklist_destroy_socket(&dn_raw_mop_sklist, sk);
+ break;
+#endif /* CONFIG_DECNET_MOP */
+ }
+
+ return 0;
+}
+
+/*
+ * Bind does odd things with raw sockets. Its basically used to filter
+ * the incomming packets, but this differs with the different layers
+ * at which you extract packets.
+ *
+ * For Routing layer sockets, the object name is a host ordered unsigned
+ * short which is a mask for the 16 different types of possible routing
+ * packet. I'd like to also select by destination address of the packets
+ * but alas, this is rather too dificult to do at the moment.
+ */
+static int dn_raw_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_dn *addr = (struct sockaddr_dn *)uaddr;
+
+ if (addr_len != sizeof(struct sockaddr_dn))
+ return -EINVAL;
+
+ if (sk->zapped == 0)
+ return -EINVAL;
+
+ switch(sk->protocol) {
+ case DNPROTO_ROU:
+ if (addr->sdn_objnamel && (addr->sdn_objnamel != 2))
+ return -EINVAL;
+ /* Fall through here */
+ case DNPROTO_NSP:
+ if (addr->sdn_add.a_len && (addr->sdn_add.a_len != 2))
+ return -EINVAL;
+ break;
+ default:
+ return -EPROTONOSUPPORT;
+ }
+
+ if (addr->sdn_objnamel > (DN_MAXOBJL-1))
+ return -EINVAL;
+
+ if (addr->sdn_add.a_len > DN_MAXADDL)
+ return -EINVAL;
+
+
+ memcpy(&sk->protinfo.dn.addr, addr, sizeof(struct sockaddr_dn));
+
+ dn_raw_autobind(sk);
+
+ return 0;
+}
+
+/*
+ * This is to allow send() and write() to work. You set the destination address
+ * with this function.
+ */
+static int dn_raw_connect(struct socket *sock, struct sockaddr *uaddr, int addr_len, int flags)
+{
+ struct sock *sk = sock->sk;
+ struct dn_scp *scp = &sk->protinfo.dn;
+ struct sockaddr_dn *saddr = (struct sockaddr_dn *)uaddr;
+
+ if (addr_len != sizeof(struct sockaddr_dn))
+ return -EINVAL;
+
+ if (saddr->sdn_family != AF_DECnet)
+ return -EINVAL;
+
+ if (saddr->sdn_objnamel > (DN_MAXOBJL-1))
+ return -EINVAL;
+
+ if (saddr->sdn_add.a_len > DN_MAXADDL)
+ return -EINVAL;
+
+ if (sk->zapped)
+ dn_raw_autobind(sk);
+
+ memcpy(&scp->peer, saddr, sizeof(struct sockaddr_dn));
+
+ return 0;
+}
+
+/*
+ * TBD.
+ */
+static int dn_raw_sendmsg(struct socket *sock, struct msghdr *hdr, int size,
+ struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+
+ if (sk->zapped)
+ dn_raw_autobind(sk);
+
+ if (sk->protocol != DNPROTO_NSP)
+ return -EOPNOTSUPP;
+
+ return 0;
+}
+
+/*
+ * This works fine, execpt that it doesn't report the originating address
+ * or anything at the moment.
+ */
+static int dn_raw_recvmsg(struct socket *sock, struct msghdr *msg, int size,
+ int flags, struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ struct sk_buff *skb;
+ int err = 0;
+ int copied = 0;
+
+ if (sk->zapped)
+ dn_raw_autobind(sk);
+
+ if ((skb = skb_recv_datagram(sk, flags & ~MSG_DONTWAIT, flags & MSG_DONTWAIT, &err)) == NULL)
+ goto out;
+
+ copied = skb->len;
+
+ if (copied > size) {
+ copied = size;
+ msg->msg_flags |= MSG_TRUNC;
+ }
+
+ if ((err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied)) != 0) {
+ if (flags & MSG_PEEK)
+ atomic_dec(&skb->users);
+ else
+ skb_queue_head(&sk->receive_queue, skb);
+
+ goto out;
+ }
+
+ skb_free_datagram(sk, skb);
+
+out:
+ return copied ? copied : err;
+}
+
+struct proto_ops dn_raw_proto_ops = {
+ AF_DECnet,
+
+ sock_no_dup,
+ dn_raw_release,
+ dn_raw_bind,
+ dn_raw_connect,
+ sock_no_socketpair,
+ sock_no_accept,
+ sock_no_getname,
+ datagram_poll,
+ sock_no_ioctl,
+ sock_no_listen,
+ sock_no_shutdown,
+ sock_no_setsockopt,
+ sock_no_getsockopt,
+ sock_no_fcntl,
+ dn_raw_sendmsg,
+ dn_raw_recvmsg
+};
+
+#ifdef CONFIG_PROC_FS
+int dn_raw_get_info(char *buffer, char **start, off_t offset, int length, int dummy)
+{
+ int len = 0;
+ off_t pos = 0;
+ off_t begin = 0;
+ struct sock *sk;
+
+ cli();
+ for(sk = dn_raw_nsp_sklist; sk; sk = sk->next) {
+ len += sprintf(buffer+len, "NSP\n");
+
+ pos = begin + len;
+
+ if (pos < offset) {
+ len = 0;
+ begin = pos;
+ }
+
+ if (pos > offset + length)
+ goto all_done;
+
+ }
+
+ for(sk = dn_raw_routing_sklist; sk; sk = sk->next) {
+ len += sprintf(buffer+len, "ROU\n");
+
+ pos = begin + len;
+
+ if (pos < offset) {
+ len = 0;
+ begin = pos;
+ }
+
+ if (pos > offset + length)
+ goto all_done;
+ }
+
+#ifdef CONFIG_DECNET_MOP
+ for(sk = dn_raw_mop_sklist; sk; sk = sk->next) {
+ len += sprintf(buffer+len, "MOP\n");
+
+ pos = begin + len;
+
+ if (pos < offset) {
+ len = 0;
+ begin = pos;
+ }
+
+ if (pos > offset + length)
+ goto all_done;
+ }
+#endif /* CONFIG_DECNET_MOP */
+
+all_done:
+ sti();
+
+ *start = buffer + (offset - begin);
+ len -= (offset - begin);
+
+ if (len > length) len = length;
+
+ return(len);
+}
+#endif /* CONFIG_PROC_FS */
+
+void dn_raw_rx_nsp(struct sk_buff *skb)
+{
+ struct sock *sk;
+ struct sk_buff *skb2;
+ unsigned long cpuflags;
+
+ save_flags(cpuflags);
+ cli();
+ for(sk = dn_raw_nsp_sklist; sk != NULL; sk = sk->next) {
+ if (skb->len > sock_rspace(sk))
+ continue;
+ if (sk->dead)
+ continue;
+ if ((skb2 = skb_clone(skb, GFP_ATOMIC)) != NULL) {
+ skb_set_owner_r(skb2, sk);
+ __skb_queue_tail(&sk->receive_queue, skb2);
+ sk->data_ready(sk, skb->len);
+ }
+ }
+ restore_flags(cpuflags);
+}
+
+void dn_raw_rx_routing(struct sk_buff *skb)
+{
+ struct sock *sk;
+ struct sk_buff *skb2;
+ struct dn_skb_cb *cb = (struct dn_skb_cb *)skb->cb;
+ unsigned long cpuflags;
+ unsigned short rt_flagmask;
+ unsigned short objnamel;
+ struct dn_scp *scp;
+
+ save_flags(cpuflags);
+ cli();
+ for(sk = dn_raw_routing_sklist; sk != NULL; sk = sk->next) {
+ if (skb->len > sock_rspace(sk))
+ continue;
+ if (sk->dead)
+ continue;
+ scp = &sk->protinfo.dn;
+
+ rt_flagmask = *(unsigned short *)scp->addr.sdn_objname;
+ objnamel = dn_ntohs(scp->addr.sdn_objnamel);
+
+ if ((objnamel == 2) && (!((1 << (cb->rt_flags & 0x0f)) & rt_flagmask)))
+ continue;
+
+ if ((skb2 = skb_clone(skb, GFP_ATOMIC)) != NULL) {
+ skb_set_owner_r(skb2, sk);
+ __skb_queue_tail(&sk->receive_queue, skb2);
+ sk->data_ready(sk, skb->len);
+ }
+ }
+ restore_flags(cpuflags);
+}
+
+#ifdef CONFIG_DECNET_MOP
+void dn_raw_rx_mop(struct sk_buff *skb)
+{
+ struct sock *sk;
+ struct sk_buff *skb2;
+ unsigned long cpuflags;
+
+ save_flags(cpuflags);
+ cli();
+ for(sk = dn_raw_mop_sklist; sk != NULL; sk = sk->next) {
+ if (skb->len > sock_rspace(sk))
+ continue;
+ if (sk->dead)
+ continue;
+ if ((skb2 = skb_clone(skb, GFP_ATOMIC)) != NULL) {
+ skb_set_owner_r(skb2, sk);
+ __skb_queue_tail(&sk->receive_queue, skb2);
+ sk->data_ready(sk, skb->len);
+ }
+ }
+ restore_flags(cpuflags);
+}
+#endif /* CONFIG_DECNET_MOP */
diff --git a/net/decnet/dn_route.c b/net/decnet/dn_route.c
new file mode 100644
index 000000000..06001b5f5
--- /dev/null
+++ b/net/decnet/dn_route.c
@@ -0,0 +1,1028 @@
+
+/*
+ * 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.
+ */
+
+/******************************************************************************
+ (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/timer.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 <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/proc_fs.h>
+#include <linux/init.h>
+#include <linux/firewall.h>
+#include <linux/rtnetlink.h>
+#include <linux/string.h>
+#include <asm/errno.h>
+#include <net/neighbour.h>
+#include <net/dst.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>
+#include <net/dn_raw.h>
+
+extern struct neigh_table dn_neigh_table;
+
+#define DN_HASHBUCKETS 16
+
+static unsigned char dn_hiord_addr[6] = {0xAA,0x00,0x04,0x00,0x00,0x00};
+
+static int dn_dst_gc(void);
+static struct dst_entry *dn_dst_check(struct dst_entry *, __u32);
+static struct dst_entry *dn_dst_reroute(struct dst_entry *, struct sk_buff *skb);
+static struct dst_entry *dn_dst_negative_advice(struct dst_entry *);
+static void dn_dst_link_failure(struct sk_buff *);
+static int dn_route_input(struct sk_buff *);
+
+static struct dn_route *dn_route_cache[DN_HASHBUCKETS];
+static struct timer_list dn_route_timer = { NULL, NULL, 0, 0L, NULL };
+int decnet_dst_gc_interval = 2;
+
+static struct dst_ops dn_dst_ops = {
+ PF_DECnet,
+ __constant_htons(ETH_P_DNA_RT),
+ 128,
+ dn_dst_gc,
+ dn_dst_check,
+ dn_dst_reroute,
+ NULL,
+ dn_dst_negative_advice,
+ dn_dst_link_failure,
+ ATOMIC_INIT(0)
+};
+
+static __inline__ unsigned dn_hash(unsigned short dest)
+{
+ unsigned short tmp = (dest&0xff) ^ (dest>>8);
+ return (tmp&0x0f) ^ (tmp>>4);
+}
+
+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_HASHBUCKETS; i++) {
+ rtp = &dn_route_cache[i];
+ for(;(rt=*rtp); rtp = &rt->u.rt_next) {
+ if (atomic_read(&rt->u.dst.use) ||
+ (now - rt->u.dst.lastuse) < expire)
+ continue;
+ *rtp = rt->u.rt_next;
+ rt->u.rt_next = NULL;
+ dst_free(&rt->u.dst);
+ }
+
+ if ((jiffies - now) > 0)
+ break;
+ }
+
+ dn_route_timer.expires = now + decnet_dst_gc_interval * HZ;
+ add_timer(&dn_route_timer);
+}
+
+static int dn_dst_gc(void)
+{
+ struct dn_route *rt, **rtp;
+ int i;
+ unsigned long now = jiffies;
+ unsigned long expire = 10 * HZ;
+
+ start_bh_atomic();
+ for(i = 0; i < DN_HASHBUCKETS; i++) {
+ rtp = &dn_route_cache[i];
+ for(; (rt=*rtp); rtp = &rt->u.rt_next) {
+ if (atomic_read(&rt->u.dst.use) ||
+ (now - rt->u.dst.lastuse) < expire)
+ continue;
+ *rtp = rt->u.rt_next;
+ rt->u.rt_next = NULL;
+ dst_free(&rt->u.dst);
+ break;
+ }
+ }
+ end_bh_atomic();
+
+ return 0;
+}
+
+static struct dst_entry *dn_dst_check(struct dst_entry *dst, __u32 cookie)
+{
+ dst_release(dst);
+ return NULL;
+}
+
+static struct dst_entry *dn_dst_reroute(struct dst_entry *dst,
+ struct sk_buff *skb)
+{
+ return NULL;
+}
+
+/*
+ * This is called through sendmsg() when you specify MSG_TRYHARD
+ * and there is already a route in cache.
+ */
+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 void dn_insert_route(struct dn_route *rt)
+{
+ unsigned hash = dn_hash(rt->rt_daddr);
+ unsigned long now = jiffies;
+
+ start_bh_atomic();
+
+ rt->u.rt_next = dn_route_cache[hash];
+ dn_route_cache[hash] = rt;
+
+ atomic_inc(&rt->u.dst.refcnt);
+ atomic_inc(&rt->u.dst.use);
+ rt->u.dst.lastuse = now;
+
+ end_bh_atomic();
+}
+
+#if defined(CONFIG_DECNET_MODULE)
+static void dn_run_flush(unsigned long dummy)
+{
+ int i;
+ struct dn_route *rt, *next;
+
+ for(i = 0; i < DN_HASHBUCKETS; i++) {
+ if ((rt = xchg(&dn_route_cache[i], NULL)) == NULL)
+ continue;
+
+ for(; rt; rt=next) {
+ next = rt->u.rt_next;
+ rt->u.rt_next = NULL;
+ dst_free((struct dst_entry *)rt);
+ }
+ }
+}
+#endif /* CONFIG_DECNET_MODULE */
+
+static int dn_route_rx_long(struct sk_buff *skb)
+{
+ struct dn_skb_cb *cb = (struct dn_skb_cb *)skb->cb;
+ unsigned char *ptr = skb->data;
+
+ if (skb->len < 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 */
+
+ if (dn_route_input(skb) == 0) {
+
+#ifdef CONFIG_DECNET_FW
+ struct neighbour *neigh = skb->dst->neighbour;
+
+ switch(call_in_firewall(PF_DECnet, skb->dev, NULL, NULL, &skb)) {
+ case FW_REJECT:
+ neigh->ops->error_report(neigh, skb);
+ return 0;
+ case FW_BLOCK:
+ default:
+ goto drop_it;
+ case FW_ACCEPT:
+ }
+#endif /* CONFIG_DECNET_FW */
+
+ return skb->dst->input(skb);
+ }
+
+drop_it:
+ kfree_skb(skb);
+ return 0;
+}
+
+
+
+static int dn_route_rx_short(struct sk_buff *skb)
+{
+ struct dn_skb_cb *cb = (struct dn_skb_cb *)skb->cb;
+ unsigned char *ptr = skb->data;
+
+ if (skb->len < 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;
+
+ if (dn_route_input(skb) == 0) {
+
+#ifdef CONFIG_DECNET_FW
+ struct neighbour *neigh = skb->dst->neighbour;
+
+ switch(call_in_firewall(PF_DECnet, skb->dev, NULL, NULL, &skb)) {
+ case FW_REJECT:
+ neigh->ops->error_report(neigh, skb);
+ return 0;
+ case FW_BLOCK:
+ default:
+ goto drop_it;
+
+ case FW_ACCEPT:
+ }
+#endif /* CONFIG_DECNET_FW */
+
+ return skb->dst->input(skb);
+ }
+
+drop_it:
+ kfree_skb(skb);
+ return 0;
+}
+
+int dn_route_rcv(struct sk_buff *skb, struct device *dev, struct packet_type *pt)
+{
+ struct dn_skb_cb *cb = (struct dn_skb_cb *)skb->cb;
+ unsigned char flags = 0;
+ int padlen = 0;
+ __u16 len = dn_ntohs(*(__u16 *)skb->data);
+ struct dn_dev *dn = (struct dn_dev *)dev->dn_ptr;
+
+ if (dn == NULL)
+ goto dump_it;
+
+ cb->stamp = jiffies;
+ cb->iif = dev->ifindex;
+
+ skb_pull(skb, 2);
+
+ if (len > skb->len)
+ goto dump_it;
+
+ skb_trim(skb, len);
+
+ flags = *skb->data;
+
+ /*
+ * If we have padding, remove it.
+ */
+ if (flags & DN_RT_F_PF) {
+ padlen = flags & ~DN_RT_F_PF;
+ 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 (dn->parms.setsrc)
+ dn->parms.setsrc(skb);
+
+ /* printk(KERN_DEBUG "dn_route_rcv: got 0x%02x from %s [%d %d %d]\n", (int)flags,
+ (dev) ? dev->name : "???", len, skb->len, padlen); */
+
+#ifdef CONFIG_DECNET_RAW
+ dn_raw_rx_routing(skb);
+#endif /* CONFIG_DECNET_RAW */
+
+ if (flags & DN_RT_PKT_CNTL) {
+ 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:
+ dn_dev_hello(skb);
+ dn_neigh_pointopoint_hello(skb);
+ return 0;
+
+ case DN_RT_PKT_L1RT:
+ case DN_RT_PKT_L2RT:
+#ifdef CONFIG_DECNET_ROUTER
+ return dn_fib_rt_message(skb);
+#else
+ break;
+#endif /* CONFIG_DECNET_ROUTER */
+ case DN_RT_PKT_ERTH:
+ dn_neigh_router_hello(skb);
+ return 0;
+
+ case DN_RT_PKT_EEDH:
+ dn_neigh_endnode_hello(skb);
+ return 0;
+ }
+ } 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:
+ if (net_ratelimit())
+ printk(KERN_DEBUG "dn_route_rcv: Dumping packet\n");
+ kfree_skb(skb);
+ return 0;
+}
+
+
+void dn_send_skb(struct sk_buff *skb)
+{
+ struct sock *sk = skb->sk;
+ struct dn_scp *scp = &sk->protinfo.dn;
+
+ if (sk == NULL) {
+ dev_queue_xmit(skb);
+ return ;
+ }
+
+ skb->h.raw = skb->data;
+
+ scp->stamp = jiffies; /* Record time packet was sent */
+
+ /* printk(KERN_DEBUG "dn_send_skb\n"); */
+
+ if (sk->dst_cache && sk->dst_cache->obsolete) {
+ dst_release(sk->dst_cache);
+ sk->dst_cache = NULL;
+ }
+
+ if (sk->dst_cache == NULL) {
+ if (dn_route_output(sk) != 0) {
+ kfree_skb(skb);
+ sk->err = EHOSTUNREACH;
+ if (!sk->dead)
+ sk->state_change(sk);
+ return;
+ }
+ }
+
+ skb->dst = dst_clone(sk->dst_cache);
+
+ sk->dst_cache->output(skb);
+}
+
+
+static int dn_output(struct sk_buff *skb)
+{
+ struct dst_entry *dst = skb->dst;
+ struct dn_route *rt = (struct dn_route *)dst;
+ struct device *dev = dst->dev;
+ struct dn_skb_cb *cb = (struct dn_skb_cb *)skb->cb;
+ int err = -EINVAL;
+
+ if (!dst->neighbour)
+ 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;
+
+ /*
+ * Filter through the outgoing firewall
+ */
+#ifdef CONFIG_DECNET_FW
+ switch(call_out_firewall(PF_DECnet, dst->dev, NULL, NULL, &skb)) {
+ case FW_REJECT:
+ err = -EPERM;
+ goto drop;
+ case FW_BLOCK:
+ default:
+ err = 0;
+ goto drop;
+ case FW_ACCEPT:
+ }
+#endif /* CONFIG_DECNET_FW */
+
+ return dst->neighbour->output(skb);
+
+error:
+ if (net_ratelimit())
+ printk(KERN_DEBUG "dn_output: This should not happen\n");
+
+#ifdef CONFIG_DECNET_FW
+drop:
+#endif
+ kfree_skb(skb);
+
+ return err;
+}
+
+#ifdef CONFIG_DECNET_ROUTER
+static int dn_l2_forward(struct sk_buff *skb)
+{
+ struct dn_skb_cb *cb = (struct dn_skb_cb *)skb->cb;
+ struct dst_entry *dst = skb->dst;
+ int err = -EINVAL;
+
+ if (!dst->neighbour)
+ goto error;
+
+ /*
+ * Hop count exceeded.
+ */
+ err = 0;
+ if (++cb->hops > 30)
+ goto drop;
+
+ /*
+ * Forwarding firewall
+ */
+#ifdef CONFIG_DECNET_FW
+ switch(call_fw_firewall(PF_DECnet, dst->dev, NULL, NULL, &skb)) {
+ case FW_REJECT:
+ dst->neighbour->ops->error_report(dst->neighbour, skb);
+ return -EPERM;
+ case FW_BLOCK:
+ default:
+ goto drop;
+ case FW_ACCEPT:
+ }
+#endif /* CONFIG_DECNET_FW */
+
+ skb->dev = 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.
+ */
+ if (cb->iif == dst->dev->ifindex)
+ cb->rt_flags |= DN_RT_F_IE;
+ else
+ cb->rt_flags &= ~DN_RT_F_IE;
+
+#ifdef CONFIG_DECNET_FW
+ switch(call_out_firewall(PF_DECnet, dst->dev, NULL, NULL, &skb)) {
+ case FW_REJECT:
+ dst->neighbour->ops->error_report(dst->neighbour, skb);
+ return -EPERM;
+ case FW_BLOCK:
+ default:
+ goto drop;
+ case FW_ACCEPT:
+ }
+#endif /* CONFIG_DECNET_FW */
+
+ return dst->neighbour->output(skb);
+
+
+error:
+ if (net_ratelimit())
+ printk(KERN_DEBUG "dn_forward: This should not happen\n");
+drop:
+ kfree_skb(skb);
+
+ return err;
+}
+
+/*
+ * Simple frontend to the l2 routing function which filters
+ * traffic not in our area when we should only do l1
+ * routing.
+ */
+static int dn_l1_forward(struct sk_buff *skb)
+{
+ struct dn_skb_cb *cb = (struct dn_skb_cb *)skb->cb;
+
+ if ((dn_ntohs(cb->dst ^ decnet_address) & 0xfc00) == 0)
+ return dn_l2_forward(skb);
+
+ kfree_skb(skb);
+ return 0;
+}
+#endif
+
+/*
+ * 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 0;
+}
+
+/*
+ * 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 = (struct dn_skb_cb *)skb->cb;
+
+ printk(KERN_DEBUG "dn_rt_bug: skb from:%04x to:%04x\n",
+ cb->src, cb->dst);
+ }
+
+ kfree_skb(skb);
+
+ return -EINVAL;
+}
+
+static int dn_route_output_slow(struct sock *sk)
+{
+ struct dn_scp *scp = &sk->protinfo.dn;
+ dn_address dest = dn_saddr2dn(&scp->peer);
+ struct dn_route *rt = NULL;
+ struct device *dev = decnet_default_device;
+ struct neighbour *neigh = NULL;
+ struct dn_dev *dn_db;
+ unsigned char addr[6];
+
+#ifdef CONFIG_DECNET_ROUTER
+ if ((decnet_node_type == DN_RT_INFO_L1RT) || (decnet_node_type == DN_RT_INFO_L2RT)) {
+#if 0
+ struct dn_fib_ent *fe = dn_fib_lookup(dest, decnet_address);
+
+ if (fe != NULL) {
+ neigh = neigh_clone(fe->neigh);
+ dn_fib_release(fe);
+ goto got_route;
+ }
+#endif
+ }
+#endif
+
+ dn_dn2eth(addr, dest);
+
+ /* Look in On-Ethernet cache first */
+ if ((neigh = dn_neigh_lookup(&dn_neigh_table, &addr)) != NULL)
+ goto got_route;
+
+ if (dev == NULL)
+ return -EINVAL;
+
+ /* FIXME: We need to change this for routing nodes */
+ /* Send to default router if that doesn't work */
+ if ((neigh = neigh_lookup(&dn_neigh_table, &addr, dev)) != NULL)
+ goto got_route;
+
+ /* Send to default device (and hope for the best) if above fail */
+ if ((neigh = __neigh_lookup(&dn_neigh_table, &addr, dev, 1)) != NULL)
+ goto got_route;
+
+
+ return -EINVAL;
+
+got_route:
+
+ if ((rt = dst_alloc(sizeof(struct dn_route), &dn_dst_ops)) == NULL) {
+ neigh_release(neigh);
+ return -EINVAL;
+ }
+
+ dn_db = (struct dn_dev *)neigh->dev->dn_ptr;
+
+ rt->rt_saddr = decnet_address;
+ rt->rt_daddr = dest;
+ rt->rt_oif = neigh->dev->ifindex;
+ rt->rt_iif = 0;
+
+ rt->u.dst.neighbour = neigh;
+ rt->u.dst.dev = neigh->dev;
+ rt->u.dst.lastuse = jiffies;
+ rt->u.dst.output = dn_output;
+ rt->u.dst.input = dn_rt_bug;
+
+ if (dn_dev_islocal(neigh->dev, rt->rt_daddr))
+ rt->u.dst.input = dn_nsp_rx;
+
+ dn_insert_route(rt);
+ sk->dst_cache = &rt->u.dst;
+
+ return 0;
+}
+
+int dn_route_output(struct sock *sk)
+{
+ struct dn_scp *scp = &sk->protinfo.dn;
+ dn_address dest = dn_saddr2dn(&scp->peer);
+ unsigned hash = dn_hash(dest);
+ struct dn_route *rt = NULL;
+ unsigned short src = dn_saddr2dn(&scp->addr);
+
+ start_bh_atomic();
+ for(rt = dn_route_cache[hash]; rt; rt = rt->u.rt_next) {
+ if ((dest == rt->rt_daddr) &&
+ (src == rt->rt_saddr) &&
+ (rt->rt_iif == 0) &&
+ (rt->rt_oif != 0)) {
+ rt->u.dst.lastuse = jiffies;
+ atomic_inc(&rt->u.dst.use);
+ atomic_inc(&rt->u.dst.refcnt);
+ end_bh_atomic();
+ sk->dst_cache = &rt->u.dst;
+ return 0;
+ }
+ }
+ end_bh_atomic();
+
+ return dn_route_output_slow(sk);
+}
+
+static int dn_route_input_slow(struct sk_buff *skb)
+{
+ struct dn_route *rt = NULL;
+ struct dn_skb_cb *cb = (struct dn_skb_cb *)skb->cb;
+ struct device *dev = skb->dev;
+ struct neighbour *neigh = NULL;
+ unsigned char addr[6];
+
+ /*
+ * 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.
+ */
+ if (dn_dev_islocal(dev, cb->src))
+ return 1;
+
+#ifdef CONFIG_DECNET_ROUTER
+ if ((decnet_node_type == DN_RT_INFO_L1RT) || (decnet_node_type == DN_RT_INFO_L2RT)) {
+#if 0
+ struct dn_fib_ent *fe = NULL;
+
+ fe = dn_fib_lookup(cb->src, cb->dst);
+
+ /* Try routing table first */
+ if (fe != NULL) {
+ neigh = neigh_clone(fe->neigh);
+ dn_fib_release(fe);
+ goto got_route;
+ }
+#endif
+ }
+#endif
+
+ dn_dn2eth(addr, cb->src);
+
+ /* Now see if we are directly connected */
+ if ((neigh = dn_neigh_lookup(&dn_neigh_table, &addr)) != NULL)
+ goto got_route;
+
+ if (dev == NULL)
+ return -EINVAL;
+
+ /* FIXME: Try the default router here .... */
+
+ if ((neigh = __neigh_lookup(&dn_neigh_table, &addr, dev, 1)) != NULL)
+ goto got_route;
+
+ return -EINVAL;
+
+got_route:
+
+ if ((rt = dst_alloc(sizeof(struct dn_route), &dn_dst_ops)) == NULL) {
+ neigh_release(neigh);
+ return -EINVAL;
+ }
+
+ rt->rt_saddr = cb->dst;
+ rt->rt_daddr = cb->src;
+ rt->rt_oif = 0;
+ rt->rt_iif = neigh->dev->ifindex;
+
+ rt->u.dst.neighbour = neigh;
+ rt->u.dst.dev = neigh->dev;
+ rt->u.dst.lastuse = jiffies;
+ rt->u.dst.output = dn_output;
+
+ switch(decnet_node_type) {
+ case DN_RT_INFO_ENDN:
+ rt->u.dst.input = dn_blackhole;
+ break;
+#ifdef CONFIG_DECNET_ROUTER
+ case DN_RT_INFO_L1RT:
+ rt->u.dst.input = dn_l1_forward;
+ break;
+ case DN_RT_INFO_L2RT:
+ rt->u.dst.input = dn_l2_forward;
+ break;
+#endif /* CONFIG_DECNET_ROUTER */
+ default:
+ rt->u.dst.input = dn_blackhole;
+ if (net_ratelimit())
+ printk(KERN_DEBUG "dn_route_input_slow: What kind of node are we?\n");
+ }
+
+ if (dn_dev_islocal(dev, cb->dst))
+ rt->u.dst.input = dn_nsp_rx;
+
+ dn_insert_route(rt);
+ skb->dst = (struct dst_entry *)rt;
+
+ return 0;
+}
+
+int dn_route_input(struct sk_buff *skb)
+{
+ struct dn_route *rt;
+ struct dn_skb_cb *cb = (struct dn_skb_cb *)skb->cb;
+ unsigned hash = dn_hash(cb->src);
+
+ if (skb->dst)
+ return 0;
+
+ for(rt = dn_route_cache[hash]; rt != NULL; rt = rt->u.rt_next) {
+ if ((rt->rt_saddr == cb->dst) &&
+ (rt->rt_daddr == cb->src) &&
+ (rt->rt_oif == 0) &&
+ (rt->rt_iif == cb->iif)) {
+ rt->u.dst.lastuse = jiffies;
+ atomic_inc(&rt->u.dst.use);
+ atomic_inc(&rt->u.dst.refcnt);
+ skb->dst = (struct dst_entry *)rt;
+ return 0;
+ }
+ }
+
+ return dn_route_input_slow(skb);
+}
+
+#ifdef CONFIG_DECNET_ROUTER
+#ifdef CONFIG_RTNETLINK
+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;
+
+ nlh = NLMSG_PUT(skb, pid, seq, event, sizeof(*r));
+ r = NLMSG_DATA(nlh);
+ nlh->nlmsg_flags = nowait ? NLM_F_MULTI : 0;
+ r->rtm_family = AF_DECnet;
+ r->rtm_dst_len = 16;
+ r->rtm_src_len = 16;
+ r->rtm_tos = 0;
+ r->rtm_table = 0;
+ r->rtm_type = 0;
+ r->rtm_scope = RT_SCOPE_UNIVERSE;
+ r->rtm_protocol = RTPROT_UNSPEC;
+ RTA_PUT(skb, RTA_DST, 2, &rt->rt_daddr);
+ RTA_PUT(skb, RTA_SRC, 2, &rt->rt_saddr);
+ if (rt->u.dst.dev)
+ RTA_PUT(skb, RTA_OIF, sizeof(int), &rt->u.dst.dev->ifindex);
+ if (rt->u.dst.window)
+ RTA_PUT(skb, RTAX_WINDOW, sizeof(unsigned), &rt->u.dst.window);
+ if (rt->u.dst.rtt)
+ RTA_PUT(skb, RTAX_RTT, sizeof(unsigned), &rt->u.dst.rtt);
+
+ nlh->nlmsg_len = skb->tail - b;
+ return skb->len;
+
+nlmsg_failure:
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+int dn_fib_rtm_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;
+ dn_address dst = 0;
+ dn_address src = 0;
+ int iif = 0;
+ int err;
+ struct sk_buff *skb;
+
+ skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
+ if (skb == NULL)
+ return -ENOBUFS;
+ skb->mac.raw = skb->data;
+
+ if (rta[RTA_SRC-1])
+ memcpy(&src, RTA_DATA(rta[RTA_SRC-1]), 2);
+ if (rta[RTA_DST-1])
+ memcpy(&dst, RTA_DATA(rta[RTA_DST-1]), 2);
+ if (rta[RTA_IIF-1])
+ memcpy(&iif, RTA_DATA(rta[RTA_IIF-1]), sizeof(int));
+
+ if (iif) {
+ struct device *dev;
+ if ((dev = dev_get_by_index(iif)) == NULL)
+ return -ENODEV;
+ if (!dev->dn_ptr)
+ return -ENODEV;
+ skb->protocol = __constant_htons(ETH_P_DNA_RT);
+ skb->dev = dev;
+ start_bh_atomic();
+ err = dn_route_input(skb);
+ end_bh_atomic();
+ 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));
+ err = -EOPNOTSUPP;
+ }
+ if (err) {
+ kfree_skb(skb);
+ return err;
+ }
+ skb->dst = &rt->u.dst;
+
+ 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)
+ return 0;
+ if (err < 0)
+ return -EMSGSIZE;
+
+ err = netlink_unicast(rtnl, skb, NETLINK_CB(in_skb).pid, MSG_DONTWAIT);
+
+ return err;
+}
+#endif /* CONFIG_RTNETLINK */
+#endif /* CONFIG_DECNET_ROUTER */
+
+#ifdef CONFIG_PROC_FS
+
+static int decnet_cache_get_info(char *buffer, char **start, off_t offset, int length, int dummy)
+{
+ int len = 0;
+ off_t pos = 0;
+ off_t begin = 0;
+ struct dn_route *rt;
+ int i;
+ char buf1[DN_ASCBUF_LEN], buf2[DN_ASCBUF_LEN];
+
+ start_bh_atomic();
+ for(i = 0; i < DN_HASHBUCKETS; i++) {
+ rt = dn_route_cache[i];
+ for(; rt != NULL; rt = rt->u.rt_next) {
+ len += sprintf(buffer + len, "%-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.use),
+ atomic_read(&rt->u.dst.refcnt),
+ (int)rt->u.dst.rtt
+ );
+
+ pos = begin + len;
+
+ if (pos < offset) {
+ len = 0;
+ begin = pos;
+ }
+ if (pos > offset + length)
+ break;
+ }
+ if (pos > offset + length)
+ break;
+ }
+ end_bh_atomic();
+
+ *start = buffer + (offset - begin);
+ len -= (offset - begin);
+
+ if (len > length) len = length;
+
+ return(len);
+}
+
+static struct proc_dir_entry proc_net_decnet_cache = {
+ PROC_NET_DN_CACHE, 12, "decnet_cache",
+ S_IFREG | S_IRUGO, 1, 0, 0,
+ 0, &proc_net_inode_operations,
+ decnet_cache_get_info
+};
+
+#endif /* CONFIG_PROC_FS */
+
+void __init dn_route_init(void)
+{
+ memset(dn_route_cache, 0, sizeof(struct dn_route *) * DN_HASHBUCKETS);
+
+ dn_route_timer.function = dn_dst_check_expire;
+ dn_route_timer.expires = jiffies + decnet_dst_gc_interval * HZ;
+ add_timer(&dn_route_timer);
+
+#ifdef CONFIG_PROC_FS
+ proc_net_register(&proc_net_decnet_cache);
+#endif /* CONFIG_PROC_FS */
+}
+
+#ifdef CONFIG_DECNET_MODULE
+void dn_route_cleanup(void)
+{
+ del_timer(&dn_route_timer);
+ dn_run_flush(0);
+#ifdef CONFIG_PROC_FS
+ proc_net_unregister(PROC_NET_DN_CACHE);
+#endif /* CONFIG_PROC_FS */
+}
+#endif /* CONFIG_DECNET_MODULE */
diff --git a/net/decnet/dn_timer.c b/net/decnet/dn_timer.c
new file mode 100644
index 000000000..8cfeaee70
--- /dev/null
+++ b/net/decnet/dn_timer.c
@@ -0,0 +1,164 @@
+/*
+ * 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
+ */
+#include <linux/config.h>
+#include <linux/net.h>
+#include <linux/socket.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/timer.h>
+#include <net/sock.h>
+#include <asm/spinlock.h>
+#include <asm/atomic.h>
+#include <net/dn.h>
+
+/*
+ * Fast timer is for delayed acks (200mS max)
+ * Slow timer is for everything else (n * 500mS)
+ */
+
+#define FAST_INTERVAL (HZ/5)
+#define SLOW_INTERVAL (HZ/2)
+
+static void dn_slow_timer(unsigned long arg);
+
+void dn_start_slow_timer(struct sock *sk)
+{
+ sk->timer.expires = jiffies + SLOW_INTERVAL;
+ sk->timer.function = dn_slow_timer;
+ sk->timer.data = (unsigned long)sk;
+
+ add_timer(&sk->timer);
+}
+
+void dn_stop_slow_timer(struct sock *sk)
+{
+ unsigned long cpuflags;
+
+ save_flags(cpuflags);
+ cli();
+ del_timer(&sk->timer);
+ restore_flags(cpuflags);
+}
+
+static void dn_slow_timer(unsigned long arg)
+{
+ struct sock *sk = (struct sock *)arg;
+ struct dn_scp *scp = &sk->protinfo.dn;
+
+ bh_lock_sock(sk);
+
+ if (sk->lock.users != 0) {
+ sk->timer.expires = jiffies + HZ / 10;
+ add_timer(&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.
+ */
+ 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->timer.expires = jiffies + SLOW_INTERVAL;
+
+ add_timer(&sk->timer);
+out:
+ bh_unlock_sock(sk);
+}
+
+static void dn_fast_timer(unsigned long arg)
+{
+ struct sock *sk = (struct sock *)arg;
+ struct dn_scp *scp = &sk->protinfo.dn;
+
+ bh_lock_sock(sk);
+ if (sk->lock.users != 0) {
+ scp->delack_timer.expires = jiffies + HZ / 20;
+ add_timer(&scp->delack_timer);
+ goto out;
+ }
+
+ scp->delack_pending = 0;
+
+ if (scp->delack_fxn)
+ scp->delack_fxn(sk);
+out:
+ bh_unlock_sock(sk);
+}
+
+void dn_start_fast_timer(struct sock *sk)
+{
+ struct dn_scp *scp = &sk->protinfo.dn;
+ unsigned long cpuflags;
+
+ save_flags(cpuflags);
+ cli();
+ if (!scp->delack_pending) {
+ scp->delack_pending = 1;
+ scp->delack_timer.next =
+ scp->delack_timer.prev = NULL;
+ scp->delack_timer.expires = jiffies + FAST_INTERVAL;
+ scp->delack_timer.data = (unsigned long)sk;
+ scp->delack_timer.function = dn_fast_timer;
+ add_timer(&scp->delack_timer);
+ }
+ restore_flags(cpuflags);
+}
+
+void dn_stop_fast_timer(struct sock *sk)
+{
+ struct dn_scp *scp = &sk->protinfo.dn;
+ unsigned long cpuflags;
+
+ save_flags(cpuflags);
+ cli();
+ if (scp->delack_pending) {
+ scp->delack_pending = 0;
+ del_timer(&scp->delack_timer);
+ }
+ restore_flags(cpuflags);
+}
+
diff --git a/net/decnet/sysctl_net_decnet.c b/net/decnet/sysctl_net_decnet.c
new file mode 100644
index 000000000..134ac585b
--- /dev/null
+++ b/net/decnet/sysctl_net_decnet.c
@@ -0,0 +1,473 @@
+/*
+ * 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:
+ *
+ */
+#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 <asm/uaccess.h>
+
+#include <net/dn.h>
+#include <net/dn_dev.h>
+#include <net/dn_route.h>
+
+
+int decnet_debug_level = 0;
+int decnet_time_wait = 30;
+int decnet_dn_count = 3;
+int decnet_di_count = 5;
+int decnet_dr_count = 5;
+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 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))
+
+/*
+ * 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 char *node2str(int n)
+{
+ switch(n) {
+ case DN_RT_INFO_ENDN:
+ return "EndNode\n";
+ case DN_RT_INFO_L1RT:
+ return "Level 1 Router\n";
+ case DN_RT_INFO_L2RT:
+ return "Level 2 Router\n";
+ }
+
+ return "Unknown\n";
+}
+
+static int dn_node_type_strategy(ctl_table *table, int *name, int nlen,
+ void *oldval, size_t *oldlenp,
+ void *newval, size_t newlen,
+ void **context)
+{
+ int len;
+ int type;
+
+ if (oldval && oldlenp) {
+ if (get_user(len, oldlenp))
+ return -EFAULT;
+ if (len) {
+ if (len != sizeof(int))
+ return -EINVAL;
+ if (put_user(decnet_node_type, (int *)oldval))
+ return -EFAULT;
+ }
+ }
+
+ if (newval && newlen) {
+ if (newlen != sizeof(int))
+ return -EINVAL;
+
+ if (get_user(type, (int *)newval))
+ return -EFAULT;
+
+ switch(type) {
+ case DN_RT_INFO_ENDN: /* EndNode */
+#ifdef CONFIG_DECNET_ROUTER
+ case DN_RT_INFO_L1RT: /* Level 1 Router */
+ case DN_RT_INFO_L2RT: /* Level 2 Router */
+#endif
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (decnet_node_type != type) {
+ dn_dev_devices_off();
+ decnet_node_type = type;
+ dn_dev_devices_on();
+ }
+ }
+ return 0;
+}
+
+static int dn_node_type_handler(ctl_table *table, int write,
+ struct file * filp,
+ void *buffer, size_t *lenp)
+{
+ char *s = node2str(decnet_node_type);
+ int len = strlen(s);
+
+ if (!*lenp || (filp->f_pos && !write)) {
+ *lenp = 0;
+ return 0;
+ }
+
+ if (write) {
+ char c = *(char *)buffer;
+ int type = 0;
+
+ switch(c) {
+ case 'e':
+ case 'E':
+ case '0':
+ type = DN_RT_INFO_ENDN;
+ break;
+#ifdef CONFIG_DECNET_ROUTER
+ case 'r':
+ case '1':
+ type = DN_RT_INFO_L1RT;
+ break;
+ case 'R':
+ case '2':
+ type = DN_RT_INFO_L2RT;
+ break;
+#endif /* CONFIG_DECNET_ROUTER */
+ default:
+ return -EINVAL;
+ }
+
+ if (decnet_node_type != type) {
+ dn_dev_devices_off();
+ decnet_node_type = type;
+ dn_dev_devices_on();
+ }
+
+ filp->f_pos += 1;
+
+ return 0;
+ }
+
+ if (len > *lenp) len = *lenp;
+
+ if (copy_to_user(buffer, s, len))
+ return -EFAULT;
+
+ *lenp = len;
+ filp->f_pos += len;
+
+ return 0;
+}
+
+static int dn_node_address_strategy(ctl_table *table, int *name, int nlen,
+ void *oldval, size_t *oldlenp,
+ void *newval, size_t newlen,
+ void **context)
+{
+ int 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 *)oldval))
+ return -EFAULT;
+ }
+ }
+ if (newval && newlen) {
+ if (newlen != sizeof(unsigned short))
+ return -EINVAL;
+ if (get_user(addr, (unsigned short *)newval))
+ return -EFAULT;
+
+ dn_dev_devices_off();
+
+ decnet_address = addr;
+ dn_dn2eth(decnet_ether_address, decnet_address);
+
+ dn_dev_devices_on();
+ }
+ return 0;
+}
+
+static int dn_node_address_handler(ctl_table *table, int write,
+ struct file *filp,
+ void *buffer, size_t *lenp)
+{
+ char addr[DN_ASCBUF_LEN];
+ int len;
+ dn_address dnaddr;
+
+ if (!*lenp || (filp->f_pos && !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;
+
+ if (parse_addr(&dnaddr, buffer))
+ return -EINVAL;
+
+ dn_dev_devices_off();
+
+ decnet_address = dnaddr;
+ dn_dn2eth(decnet_ether_address, decnet_address);
+
+ dn_dev_devices_on();
+
+ filp->f_pos += 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;
+ filp->f_pos += len;
+
+ return 0;
+}
+
+
+static int dn_def_dev_strategy(ctl_table *table, int *name, int nlen,
+ void *oldval, size_t *oldlenp,
+ void *newval, size_t newlen,
+ void **context)
+{
+ size_t len;
+ struct device *dev = decnet_default_device;
+ char devname[17];
+ size_t namel;
+
+ devname[0] = 0;
+
+ if (oldval && oldlenp) {
+ if (get_user(len, oldlenp))
+ return -EFAULT;
+ if (len) {
+ if (dev)
+ strcpy(devname, dev->name);
+
+ 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;
+
+ if ((dev = dev_get(devname)) == NULL)
+ return -ENODEV;
+
+ if (dev->dn_ptr == NULL)
+ return -ENODEV;
+
+ decnet_default_device = dev;
+ }
+
+ return 0;
+}
+
+
+static int dn_def_dev_handler(ctl_table *table, int write,
+ struct file * filp,
+ void *buffer, size_t *lenp)
+{
+ int len;
+ struct device *dev = decnet_default_device;
+ char devname[17];
+
+ if (!*lenp || (filp->f_pos && !write)) {
+ *lenp = 0;
+ return 0;
+ }
+
+ if (write) {
+
+ if (*lenp > 16)
+ return -E2BIG;
+
+ if (copy_from_user(devname, buffer, *lenp))
+ return -EFAULT;
+
+ devname[*lenp] = 0;
+
+ if ((dev = dev_get(devname)) == NULL)
+ return -ENODEV;
+
+ if (dev->dn_ptr == NULL)
+ return -ENODEV;
+
+ decnet_default_device = dev;
+ filp->f_pos += *lenp;
+
+ return 0;
+ }
+
+ if (dev == NULL) {
+ *lenp = 0;
+ return 0;
+ }
+
+ strcpy(devname, dev->name);
+ len = strlen(devname);
+ devname[len++] = '\n';
+
+ if (len > *lenp) len = *lenp;
+
+ if (copy_to_user(buffer, devname, len))
+ return -EFAULT;
+
+ *lenp = len;
+ filp->f_pos += len;
+
+ return 0;
+}
+
+static ctl_table dn_table[] = {
+ {NET_DECNET_NODE_TYPE, "node_type", NULL, 1, 0644, NULL,
+ dn_node_type_handler, dn_node_type_strategy, NULL,
+ NULL, NULL},
+ {NET_DECNET_NODE_ADDRESS, "node_address", NULL, 7, 0644, NULL,
+ dn_node_address_handler, dn_node_address_strategy, NULL,
+ NULL, NULL},
+ {NET_DECNET_NODE_NAME, "node_name", node_name, 7, 0644, NULL,
+ &proc_dostring, &sysctl_string, NULL, NULL, NULL},
+ {NET_DECNET_DEFAULT_DEVICE, "default_device", NULL, 16, 0644, NULL,
+ dn_def_dev_handler, dn_def_dev_strategy, NULL, NULL, NULL},
+ {NET_DECNET_TIME_WAIT, "time_wait", &decnet_time_wait,
+ sizeof(int), 0644,
+ NULL, &proc_dointvec_minmax, &sysctl_intvec, NULL,
+ &min_decnet_time_wait, &max_decnet_time_wait},
+ {NET_DECNET_DN_COUNT, "dn_count", &decnet_dn_count,
+ sizeof(int), 0644,
+ NULL, &proc_dointvec_minmax, &sysctl_intvec, NULL,
+ &min_state_count, &max_state_count},
+ {NET_DECNET_DI_COUNT, "di_count", &decnet_di_count,
+ sizeof(int), 0644,
+ NULL, &proc_dointvec_minmax, &sysctl_intvec, NULL,
+ &min_state_count, &max_state_count},
+ {NET_DECNET_DR_COUNT, "dr_count", &decnet_dr_count,
+ sizeof(int), 0644,
+ NULL, &proc_dointvec_minmax, &sysctl_intvec, NULL,
+ &min_state_count, &max_state_count},
+ {NET_DECNET_DST_GC_INTERVAL, "dst_gc_interval", &decnet_dst_gc_interval,
+ sizeof(int), 0644,
+ NULL, &proc_dointvec_minmax, &sysctl_intvec, NULL,
+ &min_decnet_dst_gc_interval, &max_decnet_dst_gc_interval},
+ {NET_DECNET_DEBUG_LEVEL, "debug", &decnet_debug_level,
+ sizeof(int), 0644,
+ NULL, &proc_dointvec, &sysctl_intvec, NULL,
+ NULL, NULL},
+ {0}
+};
+
+static ctl_table dn_dir_table[] = {
+ {NET_DECNET, "decnet", NULL, 0, 0555, dn_table},
+ {0}
+};
+
+static ctl_table dn_root_table[] = {
+ {CTL_NET, "net", NULL, 0, 0555, 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);
+}
+