diff options
Diffstat (limited to 'net')
-rw-r--r-- | net/decnet/Config.in | 13 | ||||
-rw-r--r-- | net/decnet/Makefile | 30 | ||||
-rw-r--r-- | net/decnet/TODO | 59 | ||||
-rw-r--r-- | net/decnet/af_decnet.c | 2192 | ||||
-rw-r--r-- | net/decnet/dn_dev.c | 1386 | ||||
-rw-r--r-- | net/decnet/dn_fib.c | 805 | ||||
-rw-r--r-- | net/decnet/dn_neigh.c | 633 | ||||
-rw-r--r-- | net/decnet/dn_nsp_in.c | 703 | ||||
-rw-r--r-- | net/decnet/dn_nsp_out.c | 640 | ||||
-rw-r--r-- | net/decnet/dn_raw.c | 383 | ||||
-rw-r--r-- | net/decnet/dn_route.c | 1028 | ||||
-rw-r--r-- | net/decnet/dn_timer.c | 164 | ||||
-rw-r--r-- | net/decnet/sysctl_net_decnet.c | 473 |
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); +} + |