mirror of
https://github.com/torvalds/linux
synced 2024-11-05 18:23:50 +00:00
0604475119
DRAM supply shortage and poor memory pressure tracking in TCP stack makes any change in SO_SNDBUF/SO_RCVBUF (or equivalent autotuning limits) and tcp_mem[] quite hazardous. TCPMemoryPressures SNMP counter is an indication of tcp_mem sysctl limits being hit, but only tracking number of transitions. If TCP stack behavior under stress was perfect : 1) It would maintain memory usage close to the limit. 2) Memory pressure state would be entered for short times. We certainly prefer 100 events lasting 10ms compared to one event lasting 200 seconds. This patch adds a new SNMP counter tracking cumulative duration of memory pressure events, given in ms units. $ cat /proc/sys/net/ipv4/tcp_mem 3088 4117 6176 $ grep TCP /proc/net/sockstat TCP: inuse 180 orphan 0 tw 2 alloc 234 mem 4140 $ nstat -n ; sleep 10 ; nstat |grep Pressure TcpExtTCPMemoryPressures 1700 TcpExtTCPMemoryPressuresChrono 5209 v2: Used EXPORT_SYMBOL_GPL() instead of EXPORT_SYMBOL() as David instructed. Signed-off-by: Eric Dumazet <edumazet@google.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2422 lines
53 KiB
C
2422 lines
53 KiB
C
|
|
/*
|
|
* DECnet An implementation of the DECnet protocol suite for the LINUX
|
|
* operating system. DECnet is implemented using the BSD Socket
|
|
* interface as the means of communication with the user level.
|
|
*
|
|
* DECnet Socket Layer Interface
|
|
*
|
|
* Authors: Eduardo Marcelo Serrat <emserrat@geocities.com>
|
|
* Patrick Caulfield <patrick@pandh.demon.co.uk>
|
|
*
|
|
* Changes:
|
|
* Steve Whitehouse: Copied from Eduardo Serrat and Patrick Caulfield's
|
|
* version of the code. Original copyright preserved
|
|
* below.
|
|
* Steve Whitehouse: Some bug fixes, cleaning up some code to make it
|
|
* compatible with my routing layer.
|
|
* Steve Whitehouse: Merging changes from Eduardo Serrat and Patrick
|
|
* Caulfield.
|
|
* Steve Whitehouse: Further bug fixes, checking module code still works
|
|
* with new routing layer.
|
|
* Steve Whitehouse: Additional set/get_sockopt() calls.
|
|
* Steve Whitehouse: Fixed TIOCINQ ioctl to be same as Eduardo's new
|
|
* code.
|
|
* Steve Whitehouse: recvmsg() changed to try and behave in a POSIX like
|
|
* way. Didn't manage it entirely, but its better.
|
|
* Steve Whitehouse: ditto for sendmsg().
|
|
* Steve Whitehouse: A selection of bug fixes to various things.
|
|
* Steve Whitehouse: Added TIOCOUTQ ioctl.
|
|
* Steve Whitehouse: Fixes to username2sockaddr & sockaddr2username.
|
|
* Steve Whitehouse: Fixes to connect() error returns.
|
|
* Patrick Caulfield: Fixes to delayed acceptance logic.
|
|
* David S. Miller: New socket locking
|
|
* Steve Whitehouse: Socket list hashing/locking
|
|
* Arnaldo C. Melo: use capable, not suser
|
|
* Steve Whitehouse: Removed unused code. Fix to use sk->allocation
|
|
* when required.
|
|
* Patrick Caulfield: /proc/net/decnet now has object name/number
|
|
* Steve Whitehouse: Fixed local port allocation, hashed sk list
|
|
* Matthew Wilcox: Fixes for dn_ioctl()
|
|
* Steve Whitehouse: New connect/accept logic to allow timeouts and
|
|
* prepare for sendpage etc.
|
|
*/
|
|
|
|
|
|
/******************************************************************************
|
|
(c) 1995-1998 E.M. Serrat emserrat@geocities.com
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
HISTORY:
|
|
|
|
Version Kernel Date Author/Comments
|
|
------- ------ ---- ---------------
|
|
Version 0.0.1 2.0.30 01-dic-97 Eduardo Marcelo Serrat
|
|
(emserrat@geocities.com)
|
|
|
|
First Development of DECnet Socket La-
|
|
yer for Linux. Only supports outgoing
|
|
connections.
|
|
|
|
Version 0.0.2 2.1.105 20-jun-98 Patrick J. Caulfield
|
|
(patrick@pandh.demon.co.uk)
|
|
|
|
Port to new kernel development version.
|
|
|
|
Version 0.0.3 2.1.106 25-jun-98 Eduardo Marcelo Serrat
|
|
(emserrat@geocities.com)
|
|
_
|
|
Added support for incoming connections
|
|
so we can start developing server apps
|
|
on Linux.
|
|
-
|
|
Module Support
|
|
Version 0.0.4 2.1.109 21-jul-98 Eduardo Marcelo Serrat
|
|
(emserrat@geocities.com)
|
|
_
|
|
Added support for X11R6.4. Now we can
|
|
use DECnet transport for X on Linux!!!
|
|
-
|
|
Version 0.0.5 2.1.110 01-aug-98 Eduardo Marcelo Serrat
|
|
(emserrat@geocities.com)
|
|
Removed bugs on flow control
|
|
Removed bugs on incoming accessdata
|
|
order
|
|
-
|
|
Version 0.0.6 2.1.110 07-aug-98 Eduardo Marcelo Serrat
|
|
dn_recvmsg fixes
|
|
|
|
Patrick J. Caulfield
|
|
dn_bind fixes
|
|
*******************************************************************************/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/types.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/socket.h>
|
|
#include <linux/in.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/sched/signal.h>
|
|
#include <linux/timer.h>
|
|
#include <linux/string.h>
|
|
#include <linux/sockios.h>
|
|
#include <linux/net.h>
|
|
#include <linux/netdevice.h>
|
|
#include <linux/inet.h>
|
|
#include <linux/route.h>
|
|
#include <linux/netfilter.h>
|
|
#include <linux/seq_file.h>
|
|
#include <net/sock.h>
|
|
#include <net/tcp_states.h>
|
|
#include <net/flow.h>
|
|
#include <asm/ioctls.h>
|
|
#include <linux/capability.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 <linux/jiffies.h>
|
|
#include <net/net_namespace.h>
|
|
#include <net/neighbour.h>
|
|
#include <net/dst.h>
|
|
#include <net/fib_rules.h>
|
|
#include <net/tcp.h>
|
|
#include <net/dn.h>
|
|
#include <net/dn_nsp.h>
|
|
#include <net/dn_dev.h>
|
|
#include <net/dn_route.h>
|
|
#include <net/dn_fib.h>
|
|
#include <net/dn_neigh.h>
|
|
|
|
struct dn_sock {
|
|
struct sock sk;
|
|
struct dn_scp scp;
|
|
};
|
|
|
|
static void dn_keepalive(struct sock *sk);
|
|
|
|
#define DN_SK_HASH_SHIFT 8
|
|
#define DN_SK_HASH_SIZE (1 << DN_SK_HASH_SHIFT)
|
|
#define DN_SK_HASH_MASK (DN_SK_HASH_SIZE - 1)
|
|
|
|
|
|
static const struct proto_ops dn_proto_ops;
|
|
static DEFINE_RWLOCK(dn_hash_lock);
|
|
static struct hlist_head dn_sk_hash[DN_SK_HASH_SIZE];
|
|
static struct hlist_head dn_wild_sk;
|
|
static atomic_long_t decnet_memory_allocated;
|
|
|
|
static int __dn_setsockopt(struct socket *sock, int level, int optname, char __user *optval, unsigned int optlen, int flags);
|
|
static int __dn_getsockopt(struct socket *sock, int level, int optname, char __user *optval, int __user *optlen, int flags);
|
|
|
|
static struct hlist_head *dn_find_list(struct sock *sk)
|
|
{
|
|
struct dn_scp *scp = DN_SK(sk);
|
|
|
|
if (scp->addr.sdn_flags & SDF_WILD)
|
|
return hlist_empty(&dn_wild_sk) ? &dn_wild_sk : NULL;
|
|
|
|
return &dn_sk_hash[le16_to_cpu(scp->addrloc) & DN_SK_HASH_MASK];
|
|
}
|
|
|
|
/*
|
|
* Valid ports are those greater than zero and not already in use.
|
|
*/
|
|
static int check_port(__le16 port)
|
|
{
|
|
struct sock *sk;
|
|
|
|
if (port == 0)
|
|
return -1;
|
|
|
|
sk_for_each(sk, &dn_sk_hash[le16_to_cpu(port) & DN_SK_HASH_MASK]) {
|
|
struct dn_scp *scp = DN_SK(sk);
|
|
if (scp->addrloc == port)
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static unsigned short port_alloc(struct sock *sk)
|
|
{
|
|
struct dn_scp *scp = DN_SK(sk);
|
|
static unsigned short port = 0x2000;
|
|
unsigned short i_port = port;
|
|
|
|
while(check_port(cpu_to_le16(++port)) != 0) {
|
|
if (port == i_port)
|
|
return 0;
|
|
}
|
|
|
|
scp->addrloc = cpu_to_le16(port);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Since this is only ever called from user
|
|
* level, we don't need a write_lock() version
|
|
* of this.
|
|
*/
|
|
static int dn_hash_sock(struct sock *sk)
|
|
{
|
|
struct dn_scp *scp = DN_SK(sk);
|
|
struct hlist_head *list;
|
|
int rv = -EUSERS;
|
|
|
|
BUG_ON(sk_hashed(sk));
|
|
|
|
write_lock_bh(&dn_hash_lock);
|
|
|
|
if (!scp->addrloc && !port_alloc(sk))
|
|
goto out;
|
|
|
|
rv = -EADDRINUSE;
|
|
if ((list = dn_find_list(sk)) == NULL)
|
|
goto out;
|
|
|
|
sk_add_node(sk, list);
|
|
rv = 0;
|
|
out:
|
|
write_unlock_bh(&dn_hash_lock);
|
|
return rv;
|
|
}
|
|
|
|
static void dn_unhash_sock(struct sock *sk)
|
|
{
|
|
write_lock(&dn_hash_lock);
|
|
sk_del_node_init(sk);
|
|
write_unlock(&dn_hash_lock);
|
|
}
|
|
|
|
static void dn_unhash_sock_bh(struct sock *sk)
|
|
{
|
|
write_lock_bh(&dn_hash_lock);
|
|
sk_del_node_init(sk);
|
|
write_unlock_bh(&dn_hash_lock);
|
|
}
|
|
|
|
static struct hlist_head *listen_hash(struct sockaddr_dn *addr)
|
|
{
|
|
int i;
|
|
unsigned int hash = addr->sdn_objnum;
|
|
|
|
if (hash == 0) {
|
|
hash = addr->sdn_objnamel;
|
|
for(i = 0; i < le16_to_cpu(addr->sdn_objnamel); i++) {
|
|
hash ^= addr->sdn_objname[i];
|
|
hash ^= (hash << 3);
|
|
}
|
|
}
|
|
|
|
return &dn_sk_hash[hash & DN_SK_HASH_MASK];
|
|
}
|
|
|
|
/*
|
|
* Called to transform a socket from bound (i.e. with a local address)
|
|
* into a listening socket (doesn't need a local port number) and rehashes
|
|
* based upon the object name/number.
|
|
*/
|
|
static void dn_rehash_sock(struct sock *sk)
|
|
{
|
|
struct hlist_head *list;
|
|
struct dn_scp *scp = DN_SK(sk);
|
|
|
|
if (scp->addr.sdn_flags & SDF_WILD)
|
|
return;
|
|
|
|
write_lock_bh(&dn_hash_lock);
|
|
sk_del_node_init(sk);
|
|
DN_SK(sk)->addrloc = 0;
|
|
list = listen_hash(&DN_SK(sk)->addr);
|
|
sk_add_node(sk, list);
|
|
write_unlock_bh(&dn_hash_lock);
|
|
}
|
|
|
|
int dn_sockaddr2username(struct sockaddr_dn *sdn, unsigned char *buf, unsigned char type)
|
|
{
|
|
int len = 2;
|
|
|
|
*buf++ = type;
|
|
|
|
switch (type) {
|
|
case 0:
|
|
*buf++ = sdn->sdn_objnum;
|
|
break;
|
|
case 1:
|
|
*buf++ = 0;
|
|
*buf++ = le16_to_cpu(sdn->sdn_objnamel);
|
|
memcpy(buf, sdn->sdn_objname, le16_to_cpu(sdn->sdn_objnamel));
|
|
len = 3 + le16_to_cpu(sdn->sdn_objnamel);
|
|
break;
|
|
case 2:
|
|
memset(buf, 0, 5);
|
|
buf += 5;
|
|
*buf++ = le16_to_cpu(sdn->sdn_objnamel);
|
|
memcpy(buf, sdn->sdn_objname, le16_to_cpu(sdn->sdn_objnamel));
|
|
len = 7 + le16_to_cpu(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 = cpu_to_le16(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 = cpu_to_le16(*data++);
|
|
len -= le16_to_cpu(sdn->sdn_objnamel);
|
|
|
|
if ((len < 0) || (le16_to_cpu(sdn->sdn_objnamel) > namel))
|
|
return -1;
|
|
|
|
memcpy(sdn->sdn_objname, data, le16_to_cpu(sdn->sdn_objnamel));
|
|
|
|
return size - len;
|
|
}
|
|
|
|
struct sock *dn_sklist_find_listener(struct sockaddr_dn *addr)
|
|
{
|
|
struct hlist_head *list = listen_hash(addr);
|
|
struct sock *sk;
|
|
|
|
read_lock(&dn_hash_lock);
|
|
sk_for_each(sk, list) {
|
|
struct dn_scp *scp = DN_SK(sk);
|
|
if (sk->sk_state != TCP_LISTEN)
|
|
continue;
|
|
if (scp->addr.sdn_objnum) {
|
|
if (scp->addr.sdn_objnum != addr->sdn_objnum)
|
|
continue;
|
|
} else {
|
|
if (addr->sdn_objnum)
|
|
continue;
|
|
if (scp->addr.sdn_objnamel != addr->sdn_objnamel)
|
|
continue;
|
|
if (memcmp(scp->addr.sdn_objname, addr->sdn_objname, le16_to_cpu(addr->sdn_objnamel)) != 0)
|
|
continue;
|
|
}
|
|
sock_hold(sk);
|
|
read_unlock(&dn_hash_lock);
|
|
return sk;
|
|
}
|
|
|
|
sk = sk_head(&dn_wild_sk);
|
|
if (sk) {
|
|
if (sk->sk_state == TCP_LISTEN)
|
|
sock_hold(sk);
|
|
else
|
|
sk = NULL;
|
|
}
|
|
|
|
read_unlock(&dn_hash_lock);
|
|
return sk;
|
|
}
|
|
|
|
struct sock *dn_find_by_skb(struct sk_buff *skb)
|
|
{
|
|
struct dn_skb_cb *cb = DN_SKB_CB(skb);
|
|
struct sock *sk;
|
|
struct dn_scp *scp;
|
|
|
|
read_lock(&dn_hash_lock);
|
|
sk_for_each(sk, &dn_sk_hash[le16_to_cpu(cb->dst_port) & DN_SK_HASH_MASK]) {
|
|
scp = DN_SK(sk);
|
|
if (cb->src != dn_saddr2dn(&scp->peer))
|
|
continue;
|
|
if (cb->dst_port != scp->addrloc)
|
|
continue;
|
|
if (scp->addrrem && (cb->src_port != scp->addrrem))
|
|
continue;
|
|
sock_hold(sk);
|
|
goto found;
|
|
}
|
|
sk = NULL;
|
|
found:
|
|
read_unlock(&dn_hash_lock);
|
|
return sk;
|
|
}
|
|
|
|
|
|
|
|
static void dn_destruct(struct sock *sk)
|
|
{
|
|
struct dn_scp *scp = DN_SK(sk);
|
|
|
|
skb_queue_purge(&scp->data_xmit_queue);
|
|
skb_queue_purge(&scp->other_xmit_queue);
|
|
skb_queue_purge(&scp->other_receive_queue);
|
|
|
|
dst_release(rcu_dereference_check(sk->sk_dst_cache, 1));
|
|
}
|
|
|
|
static unsigned long dn_memory_pressure;
|
|
|
|
static void dn_enter_memory_pressure(struct sock *sk)
|
|
{
|
|
if (!dn_memory_pressure) {
|
|
dn_memory_pressure = 1;
|
|
}
|
|
}
|
|
|
|
static struct proto dn_proto = {
|
|
.name = "NSP",
|
|
.owner = THIS_MODULE,
|
|
.enter_memory_pressure = dn_enter_memory_pressure,
|
|
.memory_pressure = &dn_memory_pressure,
|
|
.memory_allocated = &decnet_memory_allocated,
|
|
.sysctl_mem = sysctl_decnet_mem,
|
|
.sysctl_wmem = sysctl_decnet_wmem,
|
|
.sysctl_rmem = sysctl_decnet_rmem,
|
|
.max_header = DN_MAX_NSP_DATA_HEADER + 64,
|
|
.obj_size = sizeof(struct dn_sock),
|
|
};
|
|
|
|
static struct sock *dn_alloc_sock(struct net *net, struct socket *sock, gfp_t gfp, int kern)
|
|
{
|
|
struct dn_scp *scp;
|
|
struct sock *sk = sk_alloc(net, PF_DECnet, gfp, &dn_proto, kern);
|
|
|
|
if (!sk)
|
|
goto out;
|
|
|
|
if (sock)
|
|
sock->ops = &dn_proto_ops;
|
|
sock_init_data(sock, sk);
|
|
|
|
sk->sk_backlog_rcv = dn_nsp_backlog_rcv;
|
|
sk->sk_destruct = dn_destruct;
|
|
sk->sk_no_check_tx = 1;
|
|
sk->sk_family = PF_DECnet;
|
|
sk->sk_protocol = 0;
|
|
sk->sk_allocation = gfp;
|
|
sk->sk_sndbuf = sysctl_decnet_wmem[1];
|
|
sk->sk_rcvbuf = sysctl_decnet_rmem[1];
|
|
|
|
/* Initialization of DECnet Session Control Port */
|
|
scp = DN_SK(sk);
|
|
scp->state = DN_O; /* Open */
|
|
scp->numdat = 1; /* Next data seg to tx */
|
|
scp->numoth = 1; /* Next oth data to tx */
|
|
scp->ackxmt_dat = 0; /* Last data seg ack'ed */
|
|
scp->ackxmt_oth = 0; /* Last oth data ack'ed */
|
|
scp->ackrcv_dat = 0; /* Highest data ack recv*/
|
|
scp->ackrcv_oth = 0; /* Last oth data ack rec*/
|
|
scp->flowrem_sw = DN_SEND;
|
|
scp->flowloc_sw = DN_SEND;
|
|
scp->flowrem_dat = 0;
|
|
scp->flowrem_oth = 1;
|
|
scp->flowloc_dat = 0;
|
|
scp->flowloc_oth = 1;
|
|
scp->services_rem = 0;
|
|
scp->services_loc = 1 | NSP_FC_NONE;
|
|
scp->info_rem = 0;
|
|
scp->info_loc = 0x03; /* NSP version 4.1 */
|
|
scp->segsize_rem = 230 - DN_MAX_NSP_DATA_HEADER; /* Default: Updated by remote segsize */
|
|
scp->nonagle = 0;
|
|
scp->multi_ireq = 1;
|
|
scp->accept_mode = ACC_IMMED;
|
|
scp->addr.sdn_family = AF_DECnet;
|
|
scp->peer.sdn_family = AF_DECnet;
|
|
scp->accessdata.acc_accl = 5;
|
|
memcpy(scp->accessdata.acc_acc, "LINUX", 5);
|
|
|
|
scp->max_window = NSP_MAX_WINDOW;
|
|
scp->snd_window = NSP_MIN_WINDOW;
|
|
scp->nsp_srtt = NSP_INITIAL_SRTT;
|
|
scp->nsp_rttvar = NSP_INITIAL_RTTVAR;
|
|
scp->nsp_rxtshift = 0;
|
|
|
|
skb_queue_head_init(&scp->data_xmit_queue);
|
|
skb_queue_head_init(&scp->other_xmit_queue);
|
|
skb_queue_head_init(&scp->other_receive_queue);
|
|
|
|
scp->persist = 0;
|
|
scp->persist_fxn = NULL;
|
|
scp->keepalive = 10 * HZ;
|
|
scp->keepalive_fxn = dn_keepalive;
|
|
|
|
init_timer(&scp->delack_timer);
|
|
scp->delack_pending = 0;
|
|
scp->delack_fxn = dn_nsp_delayed_ack;
|
|
|
|
dn_start_slow_timer(sk);
|
|
out:
|
|
return sk;
|
|
}
|
|
|
|
/*
|
|
* Keepalive timer.
|
|
* FIXME: Should respond to SO_KEEPALIVE etc.
|
|
*/
|
|
static void dn_keepalive(struct sock *sk)
|
|
{
|
|
struct dn_scp *scp = DN_SK(sk);
|
|
|
|
/*
|
|
* By checking the other_data transmit queue is empty
|
|
* we are double checking that we are not sending too
|
|
* many of these keepalive frames.
|
|
*/
|
|
if (skb_queue_empty(&scp->other_xmit_queue))
|
|
dn_nsp_send_link(sk, DN_NOCHANGE, 0);
|
|
}
|
|
|
|
|
|
/*
|
|
* Timer for shutdown/destroyed sockets.
|
|
* When socket is dead & no packets have been sent for a
|
|
* certain amount of time, they are removed by this
|
|
* routine. Also takes care of sending out DI & DC
|
|
* frames at correct times.
|
|
*/
|
|
int dn_destroy_timer(struct sock *sk)
|
|
{
|
|
struct dn_scp *scp = DN_SK(sk);
|
|
|
|
scp->persist = dn_nsp_persist(sk);
|
|
|
|
switch (scp->state) {
|
|
case DN_DI:
|
|
dn_nsp_send_disc(sk, NSP_DISCINIT, 0, GFP_ATOMIC);
|
|
if (scp->nsp_rxtshift >= decnet_di_count)
|
|
scp->state = DN_CN;
|
|
return 0;
|
|
|
|
case DN_DR:
|
|
dn_nsp_send_disc(sk, NSP_DISCINIT, 0, GFP_ATOMIC);
|
|
if (scp->nsp_rxtshift >= decnet_dr_count)
|
|
scp->state = DN_DRC;
|
|
return 0;
|
|
|
|
case DN_DN:
|
|
if (scp->nsp_rxtshift < decnet_dn_count) {
|
|
/* printk(KERN_DEBUG "dn_destroy_timer: DN\n"); */
|
|
dn_nsp_send_disc(sk, NSP_DISCCONF, NSP_REASON_DC,
|
|
GFP_ATOMIC);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
scp->persist = (HZ * decnet_time_wait);
|
|
|
|
if (sk->sk_socket)
|
|
return 0;
|
|
|
|
if (time_after_eq(jiffies, scp->stamp + HZ * decnet_time_wait)) {
|
|
dn_unhash_sock(sk);
|
|
sock_put(sk);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void dn_destroy_sock(struct sock *sk)
|
|
{
|
|
struct dn_scp *scp = DN_SK(sk);
|
|
|
|
scp->nsp_rxtshift = 0; /* reset back off */
|
|
|
|
if (sk->sk_socket) {
|
|
if (sk->sk_socket->state != SS_UNCONNECTED)
|
|
sk->sk_socket->state = SS_DISCONNECTING;
|
|
}
|
|
|
|
sk->sk_state = TCP_CLOSE;
|
|
|
|
switch (scp->state) {
|
|
case DN_DN:
|
|
dn_nsp_send_disc(sk, NSP_DISCCONF, NSP_REASON_DC,
|
|
sk->sk_allocation);
|
|
scp->persist_fxn = dn_destroy_timer;
|
|
scp->persist = dn_nsp_persist(sk);
|
|
break;
|
|
case DN_CR:
|
|
scp->state = DN_DR;
|
|
goto disc_reject;
|
|
case DN_RUN:
|
|
scp->state = DN_DI;
|
|
case DN_DI:
|
|
case DN_DR:
|
|
disc_reject:
|
|
dn_nsp_send_disc(sk, NSP_DISCINIT, 0, sk->sk_allocation);
|
|
case DN_NC:
|
|
case DN_NR:
|
|
case DN_RJ:
|
|
case DN_DIC:
|
|
case DN_CN:
|
|
case DN_DRC:
|
|
case DN_CI:
|
|
case DN_CD:
|
|
scp->persist_fxn = dn_destroy_timer;
|
|
scp->persist = dn_nsp_persist(sk);
|
|
break;
|
|
default:
|
|
printk(KERN_DEBUG "DECnet: dn_destroy_sock passed socket in invalid state\n");
|
|
case DN_O:
|
|
dn_stop_slow_timer(sk);
|
|
|
|
dn_unhash_sock_bh(sk);
|
|
sock_put(sk);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
char *dn_addr2asc(__u16 addr, char *buf)
|
|
{
|
|
unsigned short node, area;
|
|
|
|
node = addr & 0x03ff;
|
|
area = addr >> 10;
|
|
sprintf(buf, "%hd.%hd", area, node);
|
|
|
|
return buf;
|
|
}
|
|
|
|
|
|
|
|
static int dn_create(struct net *net, struct socket *sock, int protocol,
|
|
int kern)
|
|
{
|
|
struct sock *sk;
|
|
|
|
if (protocol < 0 || protocol > SK_PROTOCOL_MAX)
|
|
return -EINVAL;
|
|
|
|
if (!net_eq(net, &init_net))
|
|
return -EAFNOSUPPORT;
|
|
|
|
switch (sock->type) {
|
|
case SOCK_SEQPACKET:
|
|
if (protocol != DNPROTO_NSP)
|
|
return -EPROTONOSUPPORT;
|
|
break;
|
|
case SOCK_STREAM:
|
|
break;
|
|
default:
|
|
return -ESOCKTNOSUPPORT;
|
|
}
|
|
|
|
|
|
if ((sk = dn_alloc_sock(net, sock, GFP_KERNEL, kern)) == NULL)
|
|
return -ENOBUFS;
|
|
|
|
sk->sk_protocol = protocol;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
dn_release(struct socket *sock)
|
|
{
|
|
struct sock *sk = sock->sk;
|
|
|
|
if (sk) {
|
|
sock_orphan(sk);
|
|
sock_hold(sk);
|
|
lock_sock(sk);
|
|
dn_destroy_sock(sk);
|
|
release_sock(sk);
|
|
sock_put(sk);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dn_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
|
|
{
|
|
struct sock *sk = sock->sk;
|
|
struct dn_scp *scp = DN_SK(sk);
|
|
struct sockaddr_dn *saddr = (struct sockaddr_dn *)uaddr;
|
|
struct net_device *dev, *ldev;
|
|
int rv;
|
|
|
|
if (addr_len != sizeof(struct sockaddr_dn))
|
|
return -EINVAL;
|
|
|
|
if (saddr->sdn_family != AF_DECnet)
|
|
return -EINVAL;
|
|
|
|
if (le16_to_cpu(saddr->sdn_nodeaddrl) && (le16_to_cpu(saddr->sdn_nodeaddrl) != 2))
|
|
return -EINVAL;
|
|
|
|
if (le16_to_cpu(saddr->sdn_objnamel) > DN_MAXOBJL)
|
|
return -EINVAL;
|
|
|
|
if (saddr->sdn_flags & ~SDF_WILD)
|
|
return -EINVAL;
|
|
|
|
if (!capable(CAP_NET_BIND_SERVICE) && (saddr->sdn_objnum ||
|
|
(saddr->sdn_flags & SDF_WILD)))
|
|
return -EACCES;
|
|
|
|
if (!(saddr->sdn_flags & SDF_WILD)) {
|
|
if (le16_to_cpu(saddr->sdn_nodeaddrl)) {
|
|
rcu_read_lock();
|
|
ldev = NULL;
|
|
for_each_netdev_rcu(&init_net, dev) {
|
|
if (!dev->dn_ptr)
|
|
continue;
|
|
if (dn_dev_islocal(dev, dn_saddr2dn(saddr))) {
|
|
ldev = dev;
|
|
break;
|
|
}
|
|
}
|
|
rcu_read_unlock();
|
|
if (ldev == NULL)
|
|
return -EADDRNOTAVAIL;
|
|
}
|
|
}
|
|
|
|
rv = -EINVAL;
|
|
lock_sock(sk);
|
|
if (sock_flag(sk, SOCK_ZAPPED)) {
|
|
memcpy(&scp->addr, saddr, addr_len);
|
|
sock_reset_flag(sk, SOCK_ZAPPED);
|
|
|
|
rv = dn_hash_sock(sk);
|
|
if (rv)
|
|
sock_set_flag(sk, SOCK_ZAPPED);
|
|
}
|
|
release_sock(sk);
|
|
|
|
return rv;
|
|
}
|
|
|
|
|
|
static int dn_auto_bind(struct socket *sock)
|
|
{
|
|
struct sock *sk = sock->sk;
|
|
struct dn_scp *scp = DN_SK(sk);
|
|
int rv;
|
|
|
|
sock_reset_flag(sk, SOCK_ZAPPED);
|
|
|
|
scp->addr.sdn_flags = 0;
|
|
scp->addr.sdn_objnum = 0;
|
|
|
|
/*
|
|
* This stuff is to keep compatibility with Eduardo's
|
|
* patch. I hope I can dispense with it shortly...
|
|
*/
|
|
if ((scp->accessdata.acc_accl != 0) &&
|
|
(scp->accessdata.acc_accl <= 12)) {
|
|
|
|
scp->addr.sdn_objnamel = cpu_to_le16(scp->accessdata.acc_accl);
|
|
memcpy(scp->addr.sdn_objname, scp->accessdata.acc_acc, le16_to_cpu(scp->addr.sdn_objnamel));
|
|
|
|
scp->accessdata.acc_accl = 0;
|
|
memset(scp->accessdata.acc_acc, 0, 40);
|
|
}
|
|
/* End of compatibility stuff */
|
|
|
|
scp->addr.sdn_add.a_len = cpu_to_le16(2);
|
|
rv = dn_dev_bind_default((__le16 *)scp->addr.sdn_add.a_addr);
|
|
if (rv == 0) {
|
|
rv = dn_hash_sock(sk);
|
|
if (rv)
|
|
sock_set_flag(sk, SOCK_ZAPPED);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
static int dn_confirm_accept(struct sock *sk, long *timeo, gfp_t allocation)
|
|
{
|
|
struct dn_scp *scp = DN_SK(sk);
|
|
DEFINE_WAIT(wait);
|
|
int err;
|
|
|
|
if (scp->state != DN_CR)
|
|
return -EINVAL;
|
|
|
|
scp->state = DN_CC;
|
|
scp->segsize_loc = dst_metric_advmss(__sk_dst_get(sk));
|
|
dn_send_conn_conf(sk, allocation);
|
|
|
|
prepare_to_wait(sk_sleep(sk), &wait, TASK_INTERRUPTIBLE);
|
|
for(;;) {
|
|
release_sock(sk);
|
|
if (scp->state == DN_CC)
|
|
*timeo = schedule_timeout(*timeo);
|
|
lock_sock(sk);
|
|
err = 0;
|
|
if (scp->state == DN_RUN)
|
|
break;
|
|
err = sock_error(sk);
|
|
if (err)
|
|
break;
|
|
err = sock_intr_errno(*timeo);
|
|
if (signal_pending(current))
|
|
break;
|
|
err = -EAGAIN;
|
|
if (!*timeo)
|
|
break;
|
|
prepare_to_wait(sk_sleep(sk), &wait, TASK_INTERRUPTIBLE);
|
|
}
|
|
finish_wait(sk_sleep(sk), &wait);
|
|
if (err == 0) {
|
|
sk->sk_socket->state = SS_CONNECTED;
|
|
} else if (scp->state != DN_CC) {
|
|
sk->sk_socket->state = SS_UNCONNECTED;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static int dn_wait_run(struct sock *sk, long *timeo)
|
|
{
|
|
struct dn_scp *scp = DN_SK(sk);
|
|
DEFINE_WAIT(wait);
|
|
int err = 0;
|
|
|
|
if (scp->state == DN_RUN)
|
|
goto out;
|
|
|
|
if (!*timeo)
|
|
return -EALREADY;
|
|
|
|
prepare_to_wait(sk_sleep(sk), &wait, TASK_INTERRUPTIBLE);
|
|
for(;;) {
|
|
release_sock(sk);
|
|
if (scp->state == DN_CI || scp->state == DN_CC)
|
|
*timeo = schedule_timeout(*timeo);
|
|
lock_sock(sk);
|
|
err = 0;
|
|
if (scp->state == DN_RUN)
|
|
break;
|
|
err = sock_error(sk);
|
|
if (err)
|
|
break;
|
|
err = sock_intr_errno(*timeo);
|
|
if (signal_pending(current))
|
|
break;
|
|
err = -ETIMEDOUT;
|
|
if (!*timeo)
|
|
break;
|
|
prepare_to_wait(sk_sleep(sk), &wait, TASK_INTERRUPTIBLE);
|
|
}
|
|
finish_wait(sk_sleep(sk), &wait);
|
|
out:
|
|
if (err == 0) {
|
|
sk->sk_socket->state = SS_CONNECTED;
|
|
} else if (scp->state != DN_CI && scp->state != DN_CC) {
|
|
sk->sk_socket->state = SS_UNCONNECTED;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static int __dn_connect(struct sock *sk, struct sockaddr_dn *addr, int addrlen, long *timeo, int flags)
|
|
{
|
|
struct socket *sock = sk->sk_socket;
|
|
struct dn_scp *scp = DN_SK(sk);
|
|
int err = -EISCONN;
|
|
struct flowidn fld;
|
|
struct dst_entry *dst;
|
|
|
|
if (sock->state == SS_CONNECTED)
|
|
goto out;
|
|
|
|
if (sock->state == SS_CONNECTING) {
|
|
err = 0;
|
|
if (scp->state == DN_RUN) {
|
|
sock->state = SS_CONNECTED;
|
|
goto out;
|
|
}
|
|
err = -ECONNREFUSED;
|
|
if (scp->state != DN_CI && scp->state != DN_CC) {
|
|
sock->state = SS_UNCONNECTED;
|
|
goto out;
|
|
}
|
|
return dn_wait_run(sk, timeo);
|
|
}
|
|
|
|
err = -EINVAL;
|
|
if (scp->state != DN_O)
|
|
goto out;
|
|
|
|
if (addr == NULL || addrlen != sizeof(struct sockaddr_dn))
|
|
goto out;
|
|
if (addr->sdn_family != AF_DECnet)
|
|
goto out;
|
|
if (addr->sdn_flags & SDF_WILD)
|
|
goto out;
|
|
|
|
if (sock_flag(sk, SOCK_ZAPPED)) {
|
|
err = dn_auto_bind(sk->sk_socket);
|
|
if (err)
|
|
goto out;
|
|
}
|
|
|
|
memcpy(&scp->peer, addr, sizeof(struct sockaddr_dn));
|
|
|
|
err = -EHOSTUNREACH;
|
|
memset(&fld, 0, sizeof(fld));
|
|
fld.flowidn_oif = sk->sk_bound_dev_if;
|
|
fld.daddr = dn_saddr2dn(&scp->peer);
|
|
fld.saddr = dn_saddr2dn(&scp->addr);
|
|
dn_sk_ports_copy(&fld, scp);
|
|
fld.flowidn_proto = DNPROTO_NSP;
|
|
if (dn_route_output_sock(&sk->sk_dst_cache, &fld, sk, flags) < 0)
|
|
goto out;
|
|
dst = __sk_dst_get(sk);
|
|
sk->sk_route_caps = dst->dev->features;
|
|
sock->state = SS_CONNECTING;
|
|
scp->state = DN_CI;
|
|
scp->segsize_loc = dst_metric_advmss(dst);
|
|
|
|
dn_nsp_send_conninit(sk, NSP_CI);
|
|
err = -EINPROGRESS;
|
|
if (*timeo) {
|
|
err = dn_wait_run(sk, timeo);
|
|
}
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
static int dn_connect(struct socket *sock, struct sockaddr *uaddr, int addrlen, int flags)
|
|
{
|
|
struct sockaddr_dn *addr = (struct sockaddr_dn *)uaddr;
|
|
struct sock *sk = sock->sk;
|
|
int err;
|
|
long timeo = sock_sndtimeo(sk, flags & O_NONBLOCK);
|
|
|
|
lock_sock(sk);
|
|
err = __dn_connect(sk, addr, addrlen, &timeo, 0);
|
|
release_sock(sk);
|
|
|
|
return err;
|
|
}
|
|
|
|
static inline int dn_check_state(struct sock *sk, struct sockaddr_dn *addr, int addrlen, long *timeo, int flags)
|
|
{
|
|
struct dn_scp *scp = DN_SK(sk);
|
|
|
|
switch (scp->state) {
|
|
case DN_RUN:
|
|
return 0;
|
|
case DN_CR:
|
|
return dn_confirm_accept(sk, timeo, sk->sk_allocation);
|
|
case DN_CI:
|
|
case DN_CC:
|
|
return dn_wait_run(sk, timeo);
|
|
case DN_O:
|
|
return __dn_connect(sk, addr, addrlen, timeo, flags);
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
|
|
static void dn_access_copy(struct sk_buff *skb, struct accessdata_dn *acc)
|
|
{
|
|
unsigned char *ptr = skb->data;
|
|
|
|
acc->acc_userl = *ptr++;
|
|
memcpy(&acc->acc_user, ptr, acc->acc_userl);
|
|
ptr += acc->acc_userl;
|
|
|
|
acc->acc_passl = *ptr++;
|
|
memcpy(&acc->acc_pass, ptr, acc->acc_passl);
|
|
ptr += acc->acc_passl;
|
|
|
|
acc->acc_accl = *ptr++;
|
|
memcpy(&acc->acc_acc, ptr, acc->acc_accl);
|
|
|
|
skb_pull(skb, acc->acc_accl + acc->acc_passl + acc->acc_userl + 3);
|
|
|
|
}
|
|
|
|
static void dn_user_copy(struct sk_buff *skb, struct optdata_dn *opt)
|
|
{
|
|
unsigned char *ptr = skb->data;
|
|
u16 len = *ptr++; /* yes, it's 8bit on the wire */
|
|
|
|
BUG_ON(len > 16); /* we've checked the contents earlier */
|
|
opt->opt_optl = cpu_to_le16(len);
|
|
opt->opt_status = 0;
|
|
memcpy(opt->opt_data, ptr, len);
|
|
skb_pull(skb, len + 1);
|
|
}
|
|
|
|
static struct sk_buff *dn_wait_for_connect(struct sock *sk, long *timeo)
|
|
{
|
|
DEFINE_WAIT(wait);
|
|
struct sk_buff *skb = NULL;
|
|
int err = 0;
|
|
|
|
prepare_to_wait(sk_sleep(sk), &wait, TASK_INTERRUPTIBLE);
|
|
for(;;) {
|
|
release_sock(sk);
|
|
skb = skb_dequeue(&sk->sk_receive_queue);
|
|
if (skb == NULL) {
|
|
*timeo = schedule_timeout(*timeo);
|
|
skb = skb_dequeue(&sk->sk_receive_queue);
|
|
}
|
|
lock_sock(sk);
|
|
if (skb != NULL)
|
|
break;
|
|
err = -EINVAL;
|
|
if (sk->sk_state != TCP_LISTEN)
|
|
break;
|
|
err = sock_intr_errno(*timeo);
|
|
if (signal_pending(current))
|
|
break;
|
|
err = -EAGAIN;
|
|
if (!*timeo)
|
|
break;
|
|
prepare_to_wait(sk_sleep(sk), &wait, TASK_INTERRUPTIBLE);
|
|
}
|
|
finish_wait(sk_sleep(sk), &wait);
|
|
|
|
return skb == NULL ? ERR_PTR(err) : skb;
|
|
}
|
|
|
|
static int dn_accept(struct socket *sock, struct socket *newsock, int flags,
|
|
bool kern)
|
|
{
|
|
struct sock *sk = sock->sk, *newsk;
|
|
struct sk_buff *skb = NULL;
|
|
struct dn_skb_cb *cb;
|
|
unsigned char menuver;
|
|
int err = 0;
|
|
unsigned char type;
|
|
long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);
|
|
struct dst_entry *dst;
|
|
|
|
lock_sock(sk);
|
|
|
|
if (sk->sk_state != TCP_LISTEN || DN_SK(sk)->state != DN_O) {
|
|
release_sock(sk);
|
|
return -EINVAL;
|
|
}
|
|
|
|
skb = skb_dequeue(&sk->sk_receive_queue);
|
|
if (skb == NULL) {
|
|
skb = dn_wait_for_connect(sk, &timeo);
|
|
if (IS_ERR(skb)) {
|
|
release_sock(sk);
|
|
return PTR_ERR(skb);
|
|
}
|
|
}
|
|
|
|
cb = DN_SKB_CB(skb);
|
|
sk->sk_ack_backlog--;
|
|
newsk = dn_alloc_sock(sock_net(sk), newsock, sk->sk_allocation, kern);
|
|
if (newsk == NULL) {
|
|
release_sock(sk);
|
|
kfree_skb(skb);
|
|
return -ENOBUFS;
|
|
}
|
|
release_sock(sk);
|
|
|
|
dst = skb_dst(skb);
|
|
sk_dst_set(newsk, dst);
|
|
skb_dst_set(skb, NULL);
|
|
|
|
DN_SK(newsk)->state = DN_CR;
|
|
DN_SK(newsk)->addrrem = cb->src_port;
|
|
DN_SK(newsk)->services_rem = cb->services;
|
|
DN_SK(newsk)->info_rem = cb->info;
|
|
DN_SK(newsk)->segsize_rem = cb->segsize;
|
|
DN_SK(newsk)->accept_mode = DN_SK(sk)->accept_mode;
|
|
|
|
if (DN_SK(newsk)->segsize_rem < 230)
|
|
DN_SK(newsk)->segsize_rem = 230;
|
|
|
|
if ((DN_SK(newsk)->services_rem & NSP_FC_MASK) == NSP_FC_NONE)
|
|
DN_SK(newsk)->max_window = decnet_no_fc_max_cwnd;
|
|
|
|
newsk->sk_state = TCP_LISTEN;
|
|
memcpy(&(DN_SK(newsk)->addr), &(DN_SK(sk)->addr), sizeof(struct sockaddr_dn));
|
|
|
|
/*
|
|
* If we are listening on a wild socket, we don't want
|
|
* the newly created socket on the wrong hash queue.
|
|
*/
|
|
DN_SK(newsk)->addr.sdn_flags &= ~SDF_WILD;
|
|
|
|
skb_pull(skb, dn_username2sockaddr(skb->data, skb->len, &(DN_SK(newsk)->addr), &type));
|
|
skb_pull(skb, dn_username2sockaddr(skb->data, skb->len, &(DN_SK(newsk)->peer), &type));
|
|
*(__le16 *)(DN_SK(newsk)->peer.sdn_add.a_addr) = cb->src;
|
|
*(__le16 *)(DN_SK(newsk)->addr.sdn_add.a_addr) = cb->dst;
|
|
|
|
menuver = *skb->data;
|
|
skb_pull(skb, 1);
|
|
|
|
if (menuver & DN_MENUVER_ACC)
|
|
dn_access_copy(skb, &(DN_SK(newsk)->accessdata));
|
|
|
|
if (menuver & DN_MENUVER_USR)
|
|
dn_user_copy(skb, &(DN_SK(newsk)->conndata_in));
|
|
|
|
if (menuver & DN_MENUVER_PRX)
|
|
DN_SK(newsk)->peer.sdn_flags |= SDF_PROXY;
|
|
|
|
if (menuver & DN_MENUVER_UIC)
|
|
DN_SK(newsk)->peer.sdn_flags |= SDF_UICPROXY;
|
|
|
|
kfree_skb(skb);
|
|
|
|
memcpy(&(DN_SK(newsk)->conndata_out), &(DN_SK(sk)->conndata_out),
|
|
sizeof(struct optdata_dn));
|
|
memcpy(&(DN_SK(newsk)->discdata_out), &(DN_SK(sk)->discdata_out),
|
|
sizeof(struct optdata_dn));
|
|
|
|
lock_sock(newsk);
|
|
err = dn_hash_sock(newsk);
|
|
if (err == 0) {
|
|
sock_reset_flag(newsk, SOCK_ZAPPED);
|
|
dn_send_conn_ack(newsk);
|
|
|
|
/*
|
|
* Here we use sk->sk_allocation since although the conn conf is
|
|
* for the newsk, the context is the old socket.
|
|
*/
|
|
if (DN_SK(newsk)->accept_mode == ACC_IMMED)
|
|
err = dn_confirm_accept(newsk, &timeo,
|
|
sk->sk_allocation);
|
|
}
|
|
release_sock(newsk);
|
|
return err;
|
|
}
|
|
|
|
|
|
static int dn_getname(struct socket *sock, struct sockaddr *uaddr,int *uaddr_len,int peer)
|
|
{
|
|
struct sockaddr_dn *sa = (struct sockaddr_dn *)uaddr;
|
|
struct sock *sk = sock->sk;
|
|
struct dn_scp *scp = DN_SK(sk);
|
|
|
|
*uaddr_len = sizeof(struct sockaddr_dn);
|
|
|
|
lock_sock(sk);
|
|
|
|
if (peer) {
|
|
if ((sock->state != SS_CONNECTED &&
|
|
sock->state != SS_CONNECTING) &&
|
|
scp->accept_mode == ACC_IMMED) {
|
|
release_sock(sk);
|
|
return -ENOTCONN;
|
|
}
|
|
|
|
memcpy(sa, &scp->peer, sizeof(struct sockaddr_dn));
|
|
} else {
|
|
memcpy(sa, &scp->addr, sizeof(struct sockaddr_dn));
|
|
}
|
|
|
|
release_sock(sk);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static unsigned int dn_poll(struct file *file, struct socket *sock, poll_table *wait)
|
|
{
|
|
struct sock *sk = sock->sk;
|
|
struct dn_scp *scp = DN_SK(sk);
|
|
int mask = datagram_poll(file, sock, wait);
|
|
|
|
if (!skb_queue_empty(&scp->other_receive_queue))
|
|
mask |= POLLRDBAND;
|
|
|
|
return mask;
|
|
}
|
|
|
|
static int dn_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
|
|
{
|
|
struct sock *sk = sock->sk;
|
|
struct dn_scp *scp = DN_SK(sk);
|
|
int err = -EOPNOTSUPP;
|
|
long amount = 0;
|
|
struct sk_buff *skb;
|
|
int val;
|
|
|
|
switch(cmd)
|
|
{
|
|
case SIOCGIFADDR:
|
|
case SIOCSIFADDR:
|
|
return dn_dev_ioctl(cmd, (void __user *)arg);
|
|
|
|
case SIOCATMARK:
|
|
lock_sock(sk);
|
|
val = !skb_queue_empty(&scp->other_receive_queue);
|
|
if (scp->state != DN_RUN)
|
|
val = -ENOTCONN;
|
|
release_sock(sk);
|
|
return val;
|
|
|
|
case TIOCOUTQ:
|
|
amount = sk->sk_sndbuf - sk_wmem_alloc_get(sk);
|
|
if (amount < 0)
|
|
amount = 0;
|
|
err = put_user(amount, (int __user *)arg);
|
|
break;
|
|
|
|
case TIOCINQ:
|
|
lock_sock(sk);
|
|
skb = skb_peek(&scp->other_receive_queue);
|
|
if (skb) {
|
|
amount = skb->len;
|
|
} else {
|
|
skb_queue_walk(&sk->sk_receive_queue, skb)
|
|
amount += skb->len;
|
|
}
|
|
release_sock(sk);
|
|
err = put_user(amount, (int __user *)arg);
|
|
break;
|
|
|
|
default:
|
|
err = -ENOIOCTLCMD;
|
|
break;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int dn_listen(struct socket *sock, int backlog)
|
|
{
|
|
struct sock *sk = sock->sk;
|
|
int err = -EINVAL;
|
|
|
|
lock_sock(sk);
|
|
|
|
if (sock_flag(sk, SOCK_ZAPPED))
|
|
goto out;
|
|
|
|
if ((DN_SK(sk)->state != DN_O) || (sk->sk_state == TCP_LISTEN))
|
|
goto out;
|
|
|
|
sk->sk_max_ack_backlog = backlog;
|
|
sk->sk_ack_backlog = 0;
|
|
sk->sk_state = TCP_LISTEN;
|
|
err = 0;
|
|
dn_rehash_sock(sk);
|
|
|
|
out:
|
|
release_sock(sk);
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
static int dn_shutdown(struct socket *sock, int how)
|
|
{
|
|
struct sock *sk = sock->sk;
|
|
struct dn_scp *scp = DN_SK(sk);
|
|
int err = -ENOTCONN;
|
|
|
|
lock_sock(sk);
|
|
|
|
if (sock->state == SS_UNCONNECTED)
|
|
goto out;
|
|
|
|
err = 0;
|
|
if (sock->state == SS_DISCONNECTING)
|
|
goto out;
|
|
|
|
err = -EINVAL;
|
|
if (scp->state == DN_O)
|
|
goto out;
|
|
|
|
if (how != SHUT_RDWR)
|
|
goto out;
|
|
|
|
sk->sk_shutdown = SHUTDOWN_MASK;
|
|
dn_destroy_sock(sk);
|
|
err = 0;
|
|
|
|
out:
|
|
release_sock(sk);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int dn_setsockopt(struct socket *sock, int level, int optname, char __user *optval, unsigned int optlen)
|
|
{
|
|
struct sock *sk = sock->sk;
|
|
int err;
|
|
|
|
lock_sock(sk);
|
|
err = __dn_setsockopt(sock, level, optname, optval, optlen, 0);
|
|
release_sock(sk);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int __dn_setsockopt(struct socket *sock, int level,int optname, char __user *optval, unsigned int optlen, int flags)
|
|
{
|
|
struct sock *sk = sock->sk;
|
|
struct dn_scp *scp = DN_SK(sk);
|
|
long timeo;
|
|
union {
|
|
struct optdata_dn opt;
|
|
struct accessdata_dn acc;
|
|
int mode;
|
|
unsigned long win;
|
|
int val;
|
|
unsigned char services;
|
|
unsigned char info;
|
|
} u;
|
|
int err;
|
|
|
|
if (optlen && !optval)
|
|
return -EINVAL;
|
|
|
|
if (optlen > sizeof(u))
|
|
return -EINVAL;
|
|
|
|
if (copy_from_user(&u, optval, optlen))
|
|
return -EFAULT;
|
|
|
|
switch (optname) {
|
|
case DSO_CONDATA:
|
|
if (sock->state == SS_CONNECTED)
|
|
return -EISCONN;
|
|
if ((scp->state != DN_O) && (scp->state != DN_CR))
|
|
return -EINVAL;
|
|
|
|
if (optlen != sizeof(struct optdata_dn))
|
|
return -EINVAL;
|
|
|
|
if (le16_to_cpu(u.opt.opt_optl) > 16)
|
|
return -EINVAL;
|
|
|
|
memcpy(&scp->conndata_out, &u.opt, optlen);
|
|
break;
|
|
|
|
case DSO_DISDATA:
|
|
if (sock->state != SS_CONNECTED &&
|
|
scp->accept_mode == ACC_IMMED)
|
|
return -ENOTCONN;
|
|
|
|
if (optlen != sizeof(struct optdata_dn))
|
|
return -EINVAL;
|
|
|
|
if (le16_to_cpu(u.opt.opt_optl) > 16)
|
|
return -EINVAL;
|
|
|
|
memcpy(&scp->discdata_out, &u.opt, optlen);
|
|
break;
|
|
|
|
case DSO_CONACCESS:
|
|
if (sock->state == SS_CONNECTED)
|
|
return -EISCONN;
|
|
if (scp->state != DN_O)
|
|
return -EINVAL;
|
|
|
|
if (optlen != sizeof(struct accessdata_dn))
|
|
return -EINVAL;
|
|
|
|
if ((u.acc.acc_accl > DN_MAXACCL) ||
|
|
(u.acc.acc_passl > DN_MAXACCL) ||
|
|
(u.acc.acc_userl > DN_MAXACCL))
|
|
return -EINVAL;
|
|
|
|
memcpy(&scp->accessdata, &u.acc, optlen);
|
|
break;
|
|
|
|
case DSO_ACCEPTMODE:
|
|
if (sock->state == SS_CONNECTED)
|
|
return -EISCONN;
|
|
if (scp->state != DN_O)
|
|
return -EINVAL;
|
|
|
|
if (optlen != sizeof(int))
|
|
return -EINVAL;
|
|
|
|
if ((u.mode != ACC_IMMED) && (u.mode != ACC_DEFER))
|
|
return -EINVAL;
|
|
|
|
scp->accept_mode = (unsigned char)u.mode;
|
|
break;
|
|
|
|
case DSO_CONACCEPT:
|
|
if (scp->state != DN_CR)
|
|
return -EINVAL;
|
|
timeo = sock_rcvtimeo(sk, 0);
|
|
err = dn_confirm_accept(sk, &timeo, sk->sk_allocation);
|
|
return err;
|
|
|
|
case DSO_CONREJECT:
|
|
if (scp->state != DN_CR)
|
|
return -EINVAL;
|
|
|
|
scp->state = DN_DR;
|
|
sk->sk_shutdown = SHUTDOWN_MASK;
|
|
dn_nsp_send_disc(sk, 0x38, 0, sk->sk_allocation);
|
|
break;
|
|
|
|
default:
|
|
#ifdef CONFIG_NETFILTER
|
|
return nf_setsockopt(sk, PF_DECnet, optname, optval, optlen);
|
|
#endif
|
|
case DSO_LINKINFO:
|
|
case DSO_STREAM:
|
|
case DSO_SEQPACKET:
|
|
return -ENOPROTOOPT;
|
|
|
|
case DSO_MAXWINDOW:
|
|
if (optlen != sizeof(unsigned long))
|
|
return -EINVAL;
|
|
if (u.win > NSP_MAX_WINDOW)
|
|
u.win = NSP_MAX_WINDOW;
|
|
if (u.win == 0)
|
|
return -EINVAL;
|
|
scp->max_window = u.win;
|
|
if (scp->snd_window > u.win)
|
|
scp->snd_window = u.win;
|
|
break;
|
|
|
|
case DSO_NODELAY:
|
|
if (optlen != sizeof(int))
|
|
return -EINVAL;
|
|
if (scp->nonagle == TCP_NAGLE_CORK)
|
|
return -EINVAL;
|
|
scp->nonagle = (u.val == 0) ? 0 : TCP_NAGLE_OFF;
|
|
/* if (scp->nonagle == 1) { Push pending frames } */
|
|
break;
|
|
|
|
case DSO_CORK:
|
|
if (optlen != sizeof(int))
|
|
return -EINVAL;
|
|
if (scp->nonagle == TCP_NAGLE_OFF)
|
|
return -EINVAL;
|
|
scp->nonagle = (u.val == 0) ? 0 : TCP_NAGLE_CORK;
|
|
/* if (scp->nonagle == 0) { Push pending frames } */
|
|
break;
|
|
|
|
case DSO_SERVICES:
|
|
if (optlen != sizeof(unsigned char))
|
|
return -EINVAL;
|
|
if ((u.services & ~NSP_FC_MASK) != 0x01)
|
|
return -EINVAL;
|
|
if ((u.services & NSP_FC_MASK) == NSP_FC_MASK)
|
|
return -EINVAL;
|
|
scp->services_loc = u.services;
|
|
break;
|
|
|
|
case DSO_INFO:
|
|
if (optlen != sizeof(unsigned char))
|
|
return -EINVAL;
|
|
if (u.info & 0xfc)
|
|
return -EINVAL;
|
|
scp->info_loc = u.info;
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dn_getsockopt(struct socket *sock, int level, int optname, char __user *optval, int __user *optlen)
|
|
{
|
|
struct sock *sk = sock->sk;
|
|
int err;
|
|
|
|
lock_sock(sk);
|
|
err = __dn_getsockopt(sock, level, optname, optval, optlen, 0);
|
|
release_sock(sk);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int __dn_getsockopt(struct socket *sock, int level,int optname, char __user *optval,int __user *optlen, int flags)
|
|
{
|
|
struct sock *sk = sock->sk;
|
|
struct dn_scp *scp = DN_SK(sk);
|
|
struct linkinfo_dn link;
|
|
unsigned int r_len;
|
|
void *r_data = NULL;
|
|
unsigned int val;
|
|
|
|
if(get_user(r_len , optlen))
|
|
return -EFAULT;
|
|
|
|
switch (optname) {
|
|
case DSO_CONDATA:
|
|
if (r_len > sizeof(struct optdata_dn))
|
|
r_len = sizeof(struct optdata_dn);
|
|
r_data = &scp->conndata_in;
|
|
break;
|
|
|
|
case DSO_DISDATA:
|
|
if (r_len > sizeof(struct optdata_dn))
|
|
r_len = sizeof(struct optdata_dn);
|
|
r_data = &scp->discdata_in;
|
|
break;
|
|
|
|
case DSO_CONACCESS:
|
|
if (r_len > sizeof(struct accessdata_dn))
|
|
r_len = sizeof(struct accessdata_dn);
|
|
r_data = &scp->accessdata;
|
|
break;
|
|
|
|
case DSO_ACCEPTMODE:
|
|
if (r_len > sizeof(unsigned char))
|
|
r_len = sizeof(unsigned char);
|
|
r_data = &scp->accept_mode;
|
|
break;
|
|
|
|
case DSO_LINKINFO:
|
|
if (r_len > sizeof(struct linkinfo_dn))
|
|
r_len = sizeof(struct linkinfo_dn);
|
|
|
|
memset(&link, 0, sizeof(link));
|
|
|
|
switch (sock->state) {
|
|
case SS_CONNECTING:
|
|
link.idn_linkstate = LL_CONNECTING;
|
|
break;
|
|
case SS_DISCONNECTING:
|
|
link.idn_linkstate = LL_DISCONNECTING;
|
|
break;
|
|
case SS_CONNECTED:
|
|
link.idn_linkstate = LL_RUNNING;
|
|
break;
|
|
default:
|
|
link.idn_linkstate = LL_INACTIVE;
|
|
}
|
|
|
|
link.idn_segsize = scp->segsize_rem;
|
|
r_data = &link;
|
|
break;
|
|
|
|
default:
|
|
#ifdef CONFIG_NETFILTER
|
|
{
|
|
int ret, len;
|
|
|
|
if (get_user(len, optlen))
|
|
return -EFAULT;
|
|
|
|
ret = nf_getsockopt(sk, PF_DECnet, optname, optval, &len);
|
|
if (ret >= 0)
|
|
ret = put_user(len, optlen);
|
|
return ret;
|
|
}
|
|
#endif
|
|
case DSO_STREAM:
|
|
case DSO_SEQPACKET:
|
|
case DSO_CONACCEPT:
|
|
case DSO_CONREJECT:
|
|
return -ENOPROTOOPT;
|
|
|
|
case DSO_MAXWINDOW:
|
|
if (r_len > sizeof(unsigned long))
|
|
r_len = sizeof(unsigned long);
|
|
r_data = &scp->max_window;
|
|
break;
|
|
|
|
case DSO_NODELAY:
|
|
if (r_len > sizeof(int))
|
|
r_len = sizeof(int);
|
|
val = (scp->nonagle == TCP_NAGLE_OFF);
|
|
r_data = &val;
|
|
break;
|
|
|
|
case DSO_CORK:
|
|
if (r_len > sizeof(int))
|
|
r_len = sizeof(int);
|
|
val = (scp->nonagle == TCP_NAGLE_CORK);
|
|
r_data = &val;
|
|
break;
|
|
|
|
case DSO_SERVICES:
|
|
if (r_len > sizeof(unsigned char))
|
|
r_len = sizeof(unsigned char);
|
|
r_data = &scp->services_rem;
|
|
break;
|
|
|
|
case DSO_INFO:
|
|
if (r_len > sizeof(unsigned char))
|
|
r_len = sizeof(unsigned char);
|
|
r_data = &scp->info_rem;
|
|
break;
|
|
}
|
|
|
|
if (r_data) {
|
|
if (copy_to_user(optval, r_data, r_len))
|
|
return -EFAULT;
|
|
if (put_user(r_len, optlen))
|
|
return -EFAULT;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int dn_data_ready(struct sock *sk, struct sk_buff_head *q, int flags, int target)
|
|
{
|
|
struct sk_buff *skb;
|
|
int len = 0;
|
|
|
|
if (flags & MSG_OOB)
|
|
return !skb_queue_empty(q) ? 1 : 0;
|
|
|
|
skb_queue_walk(q, skb) {
|
|
struct dn_skb_cb *cb = DN_SKB_CB(skb);
|
|
len += skb->len;
|
|
|
|
if (cb->nsp_flags & 0x40) {
|
|
/* SOCK_SEQPACKET reads to EOM */
|
|
if (sk->sk_type == SOCK_SEQPACKET)
|
|
return 1;
|
|
/* so does SOCK_STREAM unless WAITALL is specified */
|
|
if (!(flags & MSG_WAITALL))
|
|
return 1;
|
|
}
|
|
|
|
/* minimum data length for read exceeded */
|
|
if (len >= target)
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int dn_recvmsg(struct socket *sock, struct msghdr *msg, size_t size,
|
|
int flags)
|
|
{
|
|
struct sock *sk = sock->sk;
|
|
struct dn_scp *scp = DN_SK(sk);
|
|
struct sk_buff_head *queue = &sk->sk_receive_queue;
|
|
size_t target = size > 1 ? 1 : 0;
|
|
size_t copied = 0;
|
|
int rv = 0;
|
|
struct sk_buff *skb, *n;
|
|
struct dn_skb_cb *cb = NULL;
|
|
unsigned char eor = 0;
|
|
long timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT);
|
|
|
|
lock_sock(sk);
|
|
|
|
if (sock_flag(sk, SOCK_ZAPPED)) {
|
|
rv = -EADDRNOTAVAIL;
|
|
goto out;
|
|
}
|
|
|
|
if (sk->sk_shutdown & RCV_SHUTDOWN) {
|
|
rv = 0;
|
|
goto out;
|
|
}
|
|
|
|
rv = dn_check_state(sk, NULL, 0, &timeo, flags);
|
|
if (rv)
|
|
goto out;
|
|
|
|
if (flags & ~(MSG_CMSG_COMPAT|MSG_PEEK|MSG_OOB|MSG_WAITALL|MSG_DONTWAIT|MSG_NOSIGNAL)) {
|
|
rv = -EOPNOTSUPP;
|
|
goto out;
|
|
}
|
|
|
|
if (flags & MSG_OOB)
|
|
queue = &scp->other_receive_queue;
|
|
|
|
if (flags & MSG_WAITALL)
|
|
target = size;
|
|
|
|
|
|
/*
|
|
* See if there is data ready to read, sleep if there isn't
|
|
*/
|
|
for(;;) {
|
|
DEFINE_WAIT_FUNC(wait, woken_wake_function);
|
|
|
|
if (sk->sk_err)
|
|
goto out;
|
|
|
|
if (!skb_queue_empty(&scp->other_receive_queue)) {
|
|
if (!(flags & MSG_OOB)) {
|
|
msg->msg_flags |= MSG_OOB;
|
|
if (!scp->other_report) {
|
|
scp->other_report = 1;
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (scp->state != DN_RUN)
|
|
goto out;
|
|
|
|
if (signal_pending(current)) {
|
|
rv = sock_intr_errno(timeo);
|
|
goto out;
|
|
}
|
|
|
|
if (dn_data_ready(sk, queue, flags, target))
|
|
break;
|
|
|
|
if (flags & MSG_DONTWAIT) {
|
|
rv = -EWOULDBLOCK;
|
|
goto out;
|
|
}
|
|
|
|
add_wait_queue(sk_sleep(sk), &wait);
|
|
sk_set_bit(SOCKWQ_ASYNC_WAITDATA, sk);
|
|
sk_wait_event(sk, &timeo, dn_data_ready(sk, queue, flags, target), &wait);
|
|
sk_clear_bit(SOCKWQ_ASYNC_WAITDATA, sk);
|
|
remove_wait_queue(sk_sleep(sk), &wait);
|
|
}
|
|
|
|
skb_queue_walk_safe(queue, skb, n) {
|
|
unsigned int chunk = skb->len;
|
|
cb = DN_SKB_CB(skb);
|
|
|
|
if ((chunk + copied) > size)
|
|
chunk = size - copied;
|
|
|
|
if (memcpy_to_msg(msg, skb->data, chunk)) {
|
|
rv = -EFAULT;
|
|
break;
|
|
}
|
|
copied += chunk;
|
|
|
|
if (!(flags & MSG_PEEK))
|
|
skb_pull(skb, chunk);
|
|
|
|
eor = cb->nsp_flags & 0x40;
|
|
|
|
if (skb->len == 0) {
|
|
skb_unlink(skb, queue);
|
|
kfree_skb(skb);
|
|
/*
|
|
* N.B. Don't refer to skb or cb after this point
|
|
* in loop.
|
|
*/
|
|
if ((scp->flowloc_sw == DN_DONTSEND) && !dn_congested(sk)) {
|
|
scp->flowloc_sw = DN_SEND;
|
|
dn_nsp_send_link(sk, DN_SEND, 0);
|
|
}
|
|
}
|
|
|
|
if (eor) {
|
|
if (sk->sk_type == SOCK_SEQPACKET)
|
|
break;
|
|
if (!(flags & MSG_WAITALL))
|
|
break;
|
|
}
|
|
|
|
if (flags & MSG_OOB)
|
|
break;
|
|
|
|
if (copied >= target)
|
|
break;
|
|
}
|
|
|
|
rv = copied;
|
|
|
|
|
|
if (eor && (sk->sk_type == SOCK_SEQPACKET))
|
|
msg->msg_flags |= MSG_EOR;
|
|
|
|
out:
|
|
if (rv == 0)
|
|
rv = (flags & MSG_PEEK) ? -sk->sk_err : sock_error(sk);
|
|
|
|
if ((rv >= 0) && msg->msg_name) {
|
|
__sockaddr_check_size(sizeof(struct sockaddr_dn));
|
|
memcpy(msg->msg_name, &scp->peer, sizeof(struct sockaddr_dn));
|
|
msg->msg_namelen = sizeof(struct sockaddr_dn);
|
|
}
|
|
|
|
release_sock(sk);
|
|
|
|
return rv;
|
|
}
|
|
|
|
|
|
static inline int dn_queue_too_long(struct dn_scp *scp, struct sk_buff_head *queue, int flags)
|
|
{
|
|
unsigned char fctype = scp->services_rem & NSP_FC_MASK;
|
|
if (skb_queue_len(queue) >= scp->snd_window)
|
|
return 1;
|
|
if (fctype != NSP_FC_NONE) {
|
|
if (flags & MSG_OOB) {
|
|
if (scp->flowrem_oth == 0)
|
|
return 1;
|
|
} else {
|
|
if (scp->flowrem_dat == 0)
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* The DECnet spec requires that the "routing layer" accepts packets which
|
|
* are at least 230 bytes in size. This excludes any headers which the NSP
|
|
* layer might add, so we always assume that we'll be using the maximal
|
|
* length header on data packets. The variation in length is due to the
|
|
* inclusion (or not) of the two 16 bit acknowledgement fields so it doesn't
|
|
* make much practical difference.
|
|
*/
|
|
unsigned int dn_mss_from_pmtu(struct net_device *dev, int mtu)
|
|
{
|
|
unsigned int mss = 230 - DN_MAX_NSP_DATA_HEADER;
|
|
if (dev) {
|
|
struct dn_dev *dn_db = rcu_dereference_raw(dev->dn_ptr);
|
|
mtu -= LL_RESERVED_SPACE(dev);
|
|
if (dn_db->use_long)
|
|
mtu -= 21;
|
|
else
|
|
mtu -= 6;
|
|
mtu -= DN_MAX_NSP_DATA_HEADER;
|
|
} else {
|
|
/*
|
|
* 21 = long header, 16 = guess at MAC header length
|
|
*/
|
|
mtu -= (21 + DN_MAX_NSP_DATA_HEADER + 16);
|
|
}
|
|
if (mtu > mss)
|
|
mss = mtu;
|
|
return mss;
|
|
}
|
|
|
|
static inline unsigned int dn_current_mss(struct sock *sk, int flags)
|
|
{
|
|
struct dst_entry *dst = __sk_dst_get(sk);
|
|
struct dn_scp *scp = DN_SK(sk);
|
|
int mss_now = min_t(int, scp->segsize_loc, scp->segsize_rem);
|
|
|
|
/* Other data messages are limited to 16 bytes per packet */
|
|
if (flags & MSG_OOB)
|
|
return 16;
|
|
|
|
/* This works out the maximum size of segment we can send out */
|
|
if (dst) {
|
|
u32 mtu = dst_mtu(dst);
|
|
mss_now = min_t(int, dn_mss_from_pmtu(dst->dev, mtu), mss_now);
|
|
}
|
|
|
|
return mss_now;
|
|
}
|
|
|
|
/*
|
|
* N.B. We get the timeout wrong here, but then we always did get it
|
|
* wrong before and this is another step along the road to correcting
|
|
* it. It ought to get updated each time we pass through the routine,
|
|
* but in practise it probably doesn't matter too much for now.
|
|
*/
|
|
static inline struct sk_buff *dn_alloc_send_pskb(struct sock *sk,
|
|
unsigned long datalen, int noblock,
|
|
int *errcode)
|
|
{
|
|
struct sk_buff *skb = sock_alloc_send_skb(sk, datalen,
|
|
noblock, errcode);
|
|
if (skb) {
|
|
skb->protocol = htons(ETH_P_DNA_RT);
|
|
skb->pkt_type = PACKET_OUTGOING;
|
|
}
|
|
return skb;
|
|
}
|
|
|
|
static int dn_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
|
|
{
|
|
struct sock *sk = sock->sk;
|
|
struct dn_scp *scp = DN_SK(sk);
|
|
size_t mss;
|
|
struct sk_buff_head *queue = &scp->data_xmit_queue;
|
|
int flags = msg->msg_flags;
|
|
int err = 0;
|
|
size_t sent = 0;
|
|
int addr_len = msg->msg_namelen;
|
|
DECLARE_SOCKADDR(struct sockaddr_dn *, addr, msg->msg_name);
|
|
struct sk_buff *skb = NULL;
|
|
struct dn_skb_cb *cb;
|
|
size_t len;
|
|
unsigned char fctype;
|
|
long timeo;
|
|
|
|
if (flags & ~(MSG_TRYHARD|MSG_OOB|MSG_DONTWAIT|MSG_EOR|MSG_NOSIGNAL|MSG_MORE|MSG_CMSG_COMPAT))
|
|
return -EOPNOTSUPP;
|
|
|
|
if (addr_len && (addr_len != sizeof(struct sockaddr_dn)))
|
|
return -EINVAL;
|
|
|
|
lock_sock(sk);
|
|
timeo = sock_sndtimeo(sk, flags & MSG_DONTWAIT);
|
|
/*
|
|
* The only difference between stream sockets and sequenced packet
|
|
* sockets is that the stream sockets always behave as if MSG_EOR
|
|
* has been set.
|
|
*/
|
|
if (sock->type == SOCK_STREAM) {
|
|
if (flags & MSG_EOR) {
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
flags |= MSG_EOR;
|
|
}
|
|
|
|
|
|
err = dn_check_state(sk, addr, addr_len, &timeo, flags);
|
|
if (err)
|
|
goto out_err;
|
|
|
|
if (sk->sk_shutdown & SEND_SHUTDOWN) {
|
|
err = -EPIPE;
|
|
if (!(flags & MSG_NOSIGNAL))
|
|
send_sig(SIGPIPE, current, 0);
|
|
goto out_err;
|
|
}
|
|
|
|
if ((flags & MSG_TRYHARD) && sk->sk_dst_cache)
|
|
dst_negative_advice(sk);
|
|
|
|
mss = scp->segsize_rem;
|
|
fctype = scp->services_rem & NSP_FC_MASK;
|
|
|
|
mss = dn_current_mss(sk, flags);
|
|
|
|
if (flags & MSG_OOB) {
|
|
queue = &scp->other_xmit_queue;
|
|
if (size > mss) {
|
|
err = -EMSGSIZE;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
scp->persist_fxn = dn_nsp_xmit_timeout;
|
|
|
|
while(sent < size) {
|
|
err = sock_error(sk);
|
|
if (err)
|
|
goto out;
|
|
|
|
if (signal_pending(current)) {
|
|
err = sock_intr_errno(timeo);
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Calculate size that we wish to send.
|
|
*/
|
|
len = size - sent;
|
|
|
|
if (len > mss)
|
|
len = mss;
|
|
|
|
/*
|
|
* Wait for queue size to go down below the window
|
|
* size.
|
|
*/
|
|
if (dn_queue_too_long(scp, queue, flags)) {
|
|
DEFINE_WAIT_FUNC(wait, woken_wake_function);
|
|
|
|
if (flags & MSG_DONTWAIT) {
|
|
err = -EWOULDBLOCK;
|
|
goto out;
|
|
}
|
|
|
|
add_wait_queue(sk_sleep(sk), &wait);
|
|
sk_set_bit(SOCKWQ_ASYNC_WAITDATA, sk);
|
|
sk_wait_event(sk, &timeo,
|
|
!dn_queue_too_long(scp, queue, flags), &wait);
|
|
sk_clear_bit(SOCKWQ_ASYNC_WAITDATA, sk);
|
|
remove_wait_queue(sk_sleep(sk), &wait);
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Get a suitably sized skb.
|
|
* 64 is a bit of a hack really, but its larger than any
|
|
* link-layer headers and has served us well as a good
|
|
* guess as to their real length.
|
|
*/
|
|
skb = dn_alloc_send_pskb(sk, len + 64 + DN_MAX_NSP_DATA_HEADER,
|
|
flags & MSG_DONTWAIT, &err);
|
|
|
|
if (err)
|
|
break;
|
|
|
|
if (!skb)
|
|
continue;
|
|
|
|
cb = DN_SKB_CB(skb);
|
|
|
|
skb_reserve(skb, 64 + DN_MAX_NSP_DATA_HEADER);
|
|
|
|
if (memcpy_from_msg(skb_put(skb, len), msg, len)) {
|
|
err = -EFAULT;
|
|
goto out;
|
|
}
|
|
|
|
if (flags & MSG_OOB) {
|
|
cb->nsp_flags = 0x30;
|
|
if (fctype != NSP_FC_NONE)
|
|
scp->flowrem_oth--;
|
|
} else {
|
|
cb->nsp_flags = 0x00;
|
|
if (scp->seg_total == 0)
|
|
cb->nsp_flags |= 0x20;
|
|
|
|
scp->seg_total += len;
|
|
|
|
if (((sent + len) == size) && (flags & MSG_EOR)) {
|
|
cb->nsp_flags |= 0x40;
|
|
scp->seg_total = 0;
|
|
if (fctype == NSP_FC_SCMC)
|
|
scp->flowrem_dat--;
|
|
}
|
|
if (fctype == NSP_FC_SRC)
|
|
scp->flowrem_dat--;
|
|
}
|
|
|
|
sent += len;
|
|
dn_nsp_queue_xmit(sk, skb, sk->sk_allocation, flags & MSG_OOB);
|
|
skb = NULL;
|
|
|
|
scp->persist = dn_nsp_persist(sk);
|
|
|
|
}
|
|
out:
|
|
|
|
kfree_skb(skb);
|
|
|
|
release_sock(sk);
|
|
|
|
return sent ? sent : err;
|
|
|
|
out_err:
|
|
err = sk_stream_error(sk, flags, err);
|
|
release_sock(sk);
|
|
return err;
|
|
}
|
|
|
|
static int dn_device_event(struct notifier_block *this, unsigned long event,
|
|
void *ptr)
|
|
{
|
|
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
|
|
|
|
if (!net_eq(dev_net(dev), &init_net))
|
|
return NOTIFY_DONE;
|
|
|
|
switch (event) {
|
|
case NETDEV_UP:
|
|
dn_dev_up(dev);
|
|
break;
|
|
case NETDEV_DOWN:
|
|
dn_dev_down(dev);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
static struct notifier_block dn_dev_notifier = {
|
|
.notifier_call = dn_device_event,
|
|
};
|
|
|
|
static struct packet_type dn_dix_packet_type __read_mostly = {
|
|
.type = cpu_to_be16(ETH_P_DNA_RT),
|
|
.func = dn_route_rcv,
|
|
};
|
|
|
|
#ifdef CONFIG_PROC_FS
|
|
struct dn_iter_state {
|
|
int bucket;
|
|
};
|
|
|
|
static struct sock *dn_socket_get_first(struct seq_file *seq)
|
|
{
|
|
struct dn_iter_state *state = seq->private;
|
|
struct sock *n = NULL;
|
|
|
|
for(state->bucket = 0;
|
|
state->bucket < DN_SK_HASH_SIZE;
|
|
++state->bucket) {
|
|
n = sk_head(&dn_sk_hash[state->bucket]);
|
|
if (n)
|
|
break;
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
static struct sock *dn_socket_get_next(struct seq_file *seq,
|
|
struct sock *n)
|
|
{
|
|
struct dn_iter_state *state = seq->private;
|
|
|
|
n = sk_next(n);
|
|
try_again:
|
|
if (n)
|
|
goto out;
|
|
if (++state->bucket >= DN_SK_HASH_SIZE)
|
|
goto out;
|
|
n = sk_head(&dn_sk_hash[state->bucket]);
|
|
goto try_again;
|
|
out:
|
|
return n;
|
|
}
|
|
|
|
static struct sock *socket_get_idx(struct seq_file *seq, loff_t *pos)
|
|
{
|
|
struct sock *sk = dn_socket_get_first(seq);
|
|
|
|
if (sk) {
|
|
while(*pos && (sk = dn_socket_get_next(seq, sk)))
|
|
--*pos;
|
|
}
|
|
return *pos ? NULL : sk;
|
|
}
|
|
|
|
static void *dn_socket_get_idx(struct seq_file *seq, loff_t pos)
|
|
{
|
|
void *rc;
|
|
read_lock_bh(&dn_hash_lock);
|
|
rc = socket_get_idx(seq, &pos);
|
|
if (!rc) {
|
|
read_unlock_bh(&dn_hash_lock);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static void *dn_socket_seq_start(struct seq_file *seq, loff_t *pos)
|
|
{
|
|
return *pos ? dn_socket_get_idx(seq, *pos - 1) : SEQ_START_TOKEN;
|
|
}
|
|
|
|
static void *dn_socket_seq_next(struct seq_file *seq, void *v, loff_t *pos)
|
|
{
|
|
void *rc;
|
|
|
|
if (v == SEQ_START_TOKEN) {
|
|
rc = dn_socket_get_idx(seq, 0);
|
|
goto out;
|
|
}
|
|
|
|
rc = dn_socket_get_next(seq, v);
|
|
if (rc)
|
|
goto out;
|
|
read_unlock_bh(&dn_hash_lock);
|
|
out:
|
|
++*pos;
|
|
return rc;
|
|
}
|
|
|
|
static void dn_socket_seq_stop(struct seq_file *seq, void *v)
|
|
{
|
|
if (v && v != SEQ_START_TOKEN)
|
|
read_unlock_bh(&dn_hash_lock);
|
|
}
|
|
|
|
#define IS_NOT_PRINTABLE(x) ((x) < 32 || (x) > 126)
|
|
|
|
static void dn_printable_object(struct sockaddr_dn *dn, unsigned char *buf)
|
|
{
|
|
int i;
|
|
|
|
switch (le16_to_cpu(dn->sdn_objnamel)) {
|
|
case 0:
|
|
sprintf(buf, "%d", dn->sdn_objnum);
|
|
break;
|
|
default:
|
|
for (i = 0; i < le16_to_cpu(dn->sdn_objnamel); i++) {
|
|
buf[i] = dn->sdn_objname[i];
|
|
if (IS_NOT_PRINTABLE(buf[i]))
|
|
buf[i] = '.';
|
|
}
|
|
buf[i] = 0;
|
|
}
|
|
}
|
|
|
|
static char *dn_state2asc(unsigned char state)
|
|
{
|
|
switch (state) {
|
|
case DN_O:
|
|
return "OPEN";
|
|
case DN_CR:
|
|
return " CR";
|
|
case DN_DR:
|
|
return " DR";
|
|
case DN_DRC:
|
|
return " DRC";
|
|
case DN_CC:
|
|
return " CC";
|
|
case DN_CI:
|
|
return " CI";
|
|
case DN_NR:
|
|
return " NR";
|
|
case DN_NC:
|
|
return " NC";
|
|
case DN_CD:
|
|
return " CD";
|
|
case DN_RJ:
|
|
return " RJ";
|
|
case DN_RUN:
|
|
return " RUN";
|
|
case DN_DI:
|
|
return " DI";
|
|
case DN_DIC:
|
|
return " DIC";
|
|
case DN_DN:
|
|
return " DN";
|
|
case DN_CL:
|
|
return " CL";
|
|
case DN_CN:
|
|
return " CN";
|
|
}
|
|
|
|
return "????";
|
|
}
|
|
|
|
static inline void dn_socket_format_entry(struct seq_file *seq, struct sock *sk)
|
|
{
|
|
struct dn_scp *scp = DN_SK(sk);
|
|
char buf1[DN_ASCBUF_LEN];
|
|
char buf2[DN_ASCBUF_LEN];
|
|
char local_object[DN_MAXOBJL+3];
|
|
char remote_object[DN_MAXOBJL+3];
|
|
|
|
dn_printable_object(&scp->addr, local_object);
|
|
dn_printable_object(&scp->peer, remote_object);
|
|
|
|
seq_printf(seq,
|
|
"%6s/%04X %04d:%04d %04d:%04d %01d %-16s "
|
|
"%6s/%04X %04d:%04d %04d:%04d %01d %-16s %4s %s\n",
|
|
dn_addr2asc(le16_to_cpu(dn_saddr2dn(&scp->addr)), buf1),
|
|
scp->addrloc,
|
|
scp->numdat,
|
|
scp->numoth,
|
|
scp->ackxmt_dat,
|
|
scp->ackxmt_oth,
|
|
scp->flowloc_sw,
|
|
local_object,
|
|
dn_addr2asc(le16_to_cpu(dn_saddr2dn(&scp->peer)), buf2),
|
|
scp->addrrem,
|
|
scp->numdat_rcv,
|
|
scp->numoth_rcv,
|
|
scp->ackrcv_dat,
|
|
scp->ackrcv_oth,
|
|
scp->flowrem_sw,
|
|
remote_object,
|
|
dn_state2asc(scp->state),
|
|
((scp->accept_mode == ACC_IMMED) ? "IMMED" : "DEFER"));
|
|
}
|
|
|
|
static int dn_socket_seq_show(struct seq_file *seq, void *v)
|
|
{
|
|
if (v == SEQ_START_TOKEN) {
|
|
seq_puts(seq, "Local Remote\n");
|
|
} else {
|
|
dn_socket_format_entry(seq, v);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static const struct seq_operations dn_socket_seq_ops = {
|
|
.start = dn_socket_seq_start,
|
|
.next = dn_socket_seq_next,
|
|
.stop = dn_socket_seq_stop,
|
|
.show = dn_socket_seq_show,
|
|
};
|
|
|
|
static int dn_socket_seq_open(struct inode *inode, struct file *file)
|
|
{
|
|
return seq_open_private(file, &dn_socket_seq_ops,
|
|
sizeof(struct dn_iter_state));
|
|
}
|
|
|
|
static const struct file_operations dn_socket_seq_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = dn_socket_seq_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = seq_release_private,
|
|
};
|
|
#endif
|
|
|
|
static const struct net_proto_family dn_family_ops = {
|
|
.family = AF_DECnet,
|
|
.create = dn_create,
|
|
.owner = THIS_MODULE,
|
|
};
|
|
|
|
static const struct proto_ops dn_proto_ops = {
|
|
.family = AF_DECnet,
|
|
.owner = THIS_MODULE,
|
|
.release = dn_release,
|
|
.bind = dn_bind,
|
|
.connect = dn_connect,
|
|
.socketpair = sock_no_socketpair,
|
|
.accept = dn_accept,
|
|
.getname = dn_getname,
|
|
.poll = dn_poll,
|
|
.ioctl = dn_ioctl,
|
|
.listen = dn_listen,
|
|
.shutdown = dn_shutdown,
|
|
.setsockopt = dn_setsockopt,
|
|
.getsockopt = dn_getsockopt,
|
|
.sendmsg = dn_sendmsg,
|
|
.recvmsg = dn_recvmsg,
|
|
.mmap = sock_no_mmap,
|
|
.sendpage = sock_no_sendpage,
|
|
};
|
|
|
|
MODULE_DESCRIPTION("The Linux DECnet Network Protocol");
|
|
MODULE_AUTHOR("Linux DECnet Project Team");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_ALIAS_NETPROTO(PF_DECnet);
|
|
|
|
static const char banner[] __initconst = KERN_INFO
|
|
"NET4: DECnet for Linux: V.2.5.68s (C) 1995-2003 Linux DECnet Project Team\n";
|
|
|
|
static int __init decnet_init(void)
|
|
{
|
|
int rc;
|
|
|
|
printk(banner);
|
|
|
|
rc = proto_register(&dn_proto, 1);
|
|
if (rc != 0)
|
|
goto out;
|
|
|
|
dn_neigh_init();
|
|
dn_dev_init();
|
|
dn_route_init();
|
|
dn_fib_init();
|
|
|
|
sock_register(&dn_family_ops);
|
|
dev_add_pack(&dn_dix_packet_type);
|
|
register_netdevice_notifier(&dn_dev_notifier);
|
|
|
|
proc_create("decnet", S_IRUGO, init_net.proc_net, &dn_socket_seq_fops);
|
|
dn_register_sysctl();
|
|
out:
|
|
return rc;
|
|
|
|
}
|
|
module_init(decnet_init);
|
|
|
|
/*
|
|
* Prevent DECnet module unloading until its fixed properly.
|
|
* Requires an audit of the code to check for memory leaks and
|
|
* initialisation problems etc.
|
|
*/
|
|
#if 0
|
|
static void __exit decnet_exit(void)
|
|
{
|
|
sock_unregister(AF_DECnet);
|
|
rtnl_unregister_all(PF_DECnet);
|
|
dev_remove_pack(&dn_dix_packet_type);
|
|
|
|
dn_unregister_sysctl();
|
|
|
|
unregister_netdevice_notifier(&dn_dev_notifier);
|
|
|
|
dn_route_cleanup();
|
|
dn_dev_cleanup();
|
|
dn_neigh_cleanup();
|
|
dn_fib_cleanup();
|
|
|
|
remove_proc_entry("decnet", init_net.proc_net);
|
|
|
|
proto_unregister(&dn_proto);
|
|
|
|
rcu_barrier_bh(); /* Wait for completion of call_rcu_bh()'s */
|
|
}
|
|
module_exit(decnet_exit);
|
|
#endif
|