diff options
author | Ralf Baechle <ralf@linux-mips.org> | 1999-06-17 14:08:29 +0000 |
---|---|---|
committer | Ralf Baechle <ralf@linux-mips.org> | 1999-06-17 14:08:29 +0000 |
commit | 57d569635c05dc4ea9b9f1f8dcec69b9ddc989b2 (patch) | |
tree | 1f703abf7d95dcd50ee52da3b96eb1b4b2b4ea53 /net/decnet/af_decnet.c | |
parent | 59223edaa18759982db0a8aced0e77457d10c68e (diff) |
The rest of 2.3.6.
Diffstat (limited to 'net/decnet/af_decnet.c')
-rw-r--r-- | net/decnet/af_decnet.c | 2192 |
1 files changed, 2192 insertions, 0 deletions
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 |