diff options
Diffstat (limited to 'net/decnet')
-rw-r--r-- | net/decnet/Kconfig | 43 | ||||
-rw-r--r-- | net/decnet/Makefile | 10 | ||||
-rw-r--r-- | net/decnet/README | 8 | ||||
-rw-r--r-- | net/decnet/TODO | 41 | ||||
-rw-r--r-- | net/decnet/af_decnet.c | 2416 | ||||
-rw-r--r-- | net/decnet/dn_dev.c | 1446 | ||||
-rw-r--r-- | net/decnet/dn_fib.c | 791 | ||||
-rw-r--r-- | net/decnet/dn_neigh.c | 616 | ||||
-rw-r--r-- | net/decnet/dn_nsp_in.c | 917 | ||||
-rw-r--r-- | net/decnet/dn_nsp_out.c | 718 | ||||
-rw-r--r-- | net/decnet/dn_route.c | 1939 | ||||
-rw-r--r-- | net/decnet/dn_rules.c | 257 | ||||
-rw-r--r-- | net/decnet/dn_table.c | 926 | ||||
-rw-r--r-- | net/decnet/dn_timer.c | 103 | ||||
-rw-r--r-- | net/decnet/netfilter/Kconfig | 16 | ||||
-rw-r--r-- | net/decnet/netfilter/Makefile | 6 | ||||
-rw-r--r-- | net/decnet/netfilter/dn_rtmsg.c | 159 | ||||
-rw-r--r-- | net/decnet/sysctl_net_decnet.c | 372 |
18 files changed, 10784 insertions, 0 deletions
diff --git a/net/decnet/Kconfig b/net/decnet/Kconfig new file mode 100644 index 000000000..f3393e154 --- /dev/null +++ b/net/decnet/Kconfig @@ -0,0 +1,43 @@ +# +# DECnet configuration +# +config DECNET + tristate "DECnet Support" + ---help--- + The DECnet networking protocol was used in many products made by + Digital (now Compaq). It provides reliable stream and sequenced + packet communications over which run a variety of services similar + to those which run over TCP/IP. + + To find some tools to use with the kernel layer support, please + look at Patrick Caulfield's web site: + <http://linux-decnet.sourceforge.net/>. + + More detailed documentation is available in + <file:Documentation/networking/decnet.txt>. + + Be sure to say Y to "/proc file system support" and "Sysctl support" + below when using DECnet, since you will need sysctl support to aid + in configuration at run time. + + The DECnet code is also available as a module ( = code which can be + inserted in and removed from the running kernel whenever you want). + The module is called decnet. + +config DECNET_ROUTER + bool "DECnet: router support" + depends on DECNET + select FIB_RULES + ---help--- + Add support for turning your DECnet Endnode into a level 1 or 2 + router. This is an experimental, but functional option. If you + do say Y here, then make sure that you also say Y to "Kernel/User + network link driver", "Routing messages" and "Network packet + filtering". The first two are required to allow configuration via + rtnetlink (you will need Alexey Kuznetsov's iproute2 package + from <ftp://ftp.tux.org/pub/net/ip-routing/>). The "Network packet + filtering" option will be required for the forthcoming routing daemon + to work. + + See <file:Documentation/networking/decnet.txt> for more information. + diff --git a/net/decnet/Makefile b/net/decnet/Makefile new file mode 100644 index 000000000..e44003af7 --- /dev/null +++ b/net/decnet/Makefile @@ -0,0 +1,10 @@ + +obj-$(CONFIG_DECNET) += decnet.o + +decnet-y := af_decnet.o dn_nsp_in.o dn_nsp_out.o \ + dn_route.o dn_dev.o dn_neigh.o dn_timer.o +decnet-$(CONFIG_DECNET_ROUTER) += dn_fib.o dn_rules.o dn_table.o +decnet-y += sysctl_net_decnet.o + +obj-$(CONFIG_NETFILTER) += netfilter/ + diff --git a/net/decnet/README b/net/decnet/README new file mode 100644 index 000000000..60e7ec88c --- /dev/null +++ b/net/decnet/README @@ -0,0 +1,8 @@ + Linux DECnet Project + ====================== + +The documentation for this kernel subsystem is available in the +Documentation/networking subdirectory of this distribution and also +on line at http://www.chygwyn.com/DECnet/ + +Steve Whitehouse <SteveW@ACM.org> diff --git a/net/decnet/TODO b/net/decnet/TODO new file mode 100644 index 000000000..ebb5ac69d --- /dev/null +++ b/net/decnet/TODO @@ -0,0 +1,41 @@ +Steve's quick list of things that need finishing off: +[they are in no particular order and range from the trivial to the long winded] + + o Proper timeouts on each neighbour (in routing mode) rather than + just the 60 second On-Ethernet cache value. + + o Support for X.25 linklayer + + o Support for DDCMP link layer + + o The DDCMP device itself + + o PPP support (rfc1762) + + o Lots of testing with real applications + + o Verify errors etc. against POSIX 1003.1g (draft) + + o Using send/recvmsg() to get at connect/disconnect data (POSIX 1003.1g) + [maybe this should be done at socket level... the control data in the + send/recvmsg() calls should simply be a vector of set/getsockopt() + calls] + + o check MSG_CTRUNC is set where it should be. + + o Find all the commonality between DECnet and IPv4 routing code and extract + it into a small library of routines. [probably a project for 2.7.xx] + + o Add perfect socket hashing - an idea suggested by Paul Koning. Currently + we have a half-way house scheme which seems to work reasonably well, but + the full scheme is still worth implementing, its not not top of my list + right now. + + o Add session control message flow control + + o Add NSP message flow control + + o DECnet sendpages() function + + o AIO for DECnet + diff --git a/net/decnet/af_decnet.c b/net/decnet/af_decnet.c new file mode 100644 index 000000000..754484b3c --- /dev/null +++ b/net/decnet/af_decnet.c @@ -0,0 +1,2416 @@ + +/* + * 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.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/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 int 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) +{ + struct dn_scp *scp; + struct sock *sk = sk_alloc(net, PF_DECnet, gfp, &dn_proto); + + 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 (!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)) == 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) +{ + 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); + 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 == 2) + return -EINVAL; + scp->nonagle = (u.val == 0) ? 0 : 1; + /* if (scp->nonagle == 1) { Push pending frames } */ + break; + + case DSO_CORK: + if (optlen != sizeof(int)) + return -EINVAL; + if (scp->nonagle == 1) + return -EINVAL; + scp->nonagle = (u.val == 0) ? 0 : 2; + /* if (scp->nonagle == 0) { Push pending frames } */ + break; + + case DSO_SERVICES: + if (optlen != sizeof(unsigned char)) + return -EINVAL; + if ((u.services & ~NSP_FC_MASK) != 0x01) + return -EINVAL; + if ((u.services & NSP_FC_MASK) == NSP_FC_MASK) + return -EINVAL; + scp->services_loc = u.services; + break; + + case DSO_INFO: + if (optlen != sizeof(unsigned char)) + return -EINVAL; + if (u.info & 0xfc) + return -EINVAL; + scp->info_loc = u.info; + break; + } + + return 0; +} + +static int dn_getsockopt(struct socket *sock, int level, int optname, char __user *optval, int __user *optlen) +{ + struct sock *sk = sock->sk; + int err; + + lock_sock(sk); + err = __dn_getsockopt(sock, level, optname, optval, optlen, 0); + release_sock(sk); + + return err; +} + +static int __dn_getsockopt(struct socket *sock, int level,int optname, char __user *optval,int __user *optlen, int flags) +{ + struct sock *sk = sock->sk; + struct dn_scp *scp = DN_SK(sk); + struct linkinfo_dn link; + unsigned int r_len; + void *r_data = NULL; + unsigned int val; + + if(get_user(r_len , optlen)) + return -EFAULT; + + switch (optname) { + case DSO_CONDATA: + if (r_len > sizeof(struct optdata_dn)) + r_len = sizeof(struct optdata_dn); + r_data = &scp->conndata_in; + break; + + case DSO_DISDATA: + if (r_len > sizeof(struct optdata_dn)) + r_len = sizeof(struct optdata_dn); + r_data = &scp->discdata_in; + break; + + case DSO_CONACCESS: + if (r_len > sizeof(struct accessdata_dn)) + r_len = sizeof(struct accessdata_dn); + r_data = &scp->accessdata; + break; + + case DSO_ACCEPTMODE: + if (r_len > sizeof(unsigned char)) + r_len = sizeof(unsigned char); + r_data = &scp->accept_mode; + break; + + case DSO_LINKINFO: + if (r_len > sizeof(struct linkinfo_dn)) + r_len = sizeof(struct linkinfo_dn); + + 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 == 1); + r_data = &val; + break; + + case DSO_CORK: + if (r_len > sizeof(int)) + r_len = sizeof(int); + val = (scp->nonagle == 2); + r_data = &val; + break; + + case DSO_SERVICES: + if (r_len > sizeof(unsigned char)) + r_len = sizeof(unsigned char); + r_data = &scp->services_rem; + break; + + case DSO_INFO: + if (r_len > sizeof(unsigned char)) + r_len = sizeof(unsigned char); + r_data = &scp->info_rem; + break; + } + + if (r_data) { + if (copy_to_user(optval, r_data, r_len)) + return -EFAULT; + if (put_user(r_len, optlen)) + return -EFAULT; + } + + return 0; +} + + +static int dn_data_ready(struct sock *sk, struct sk_buff_head *q, int flags, int target) +{ + struct sk_buff *skb; + 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(wait); + + 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; + } + + prepare_to_wait(sk_sleep(sk), &wait, TASK_INTERRUPTIBLE); + set_bit(SOCK_ASYNC_WAITDATA, &sk->sk_socket->flags); + sk_wait_event(sk, &timeo, dn_data_ready(sk, queue, flags, target)); + clear_bit(SOCK_ASYNC_WAITDATA, &sk->sk_socket->flags); + finish_wait(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(wait); + + if (flags & MSG_DONTWAIT) { + err = -EWOULDBLOCK; + goto out; + } + + prepare_to_wait(sk_sleep(sk), &wait, TASK_INTERRUPTIBLE); + set_bit(SOCK_ASYNC_WAITDATA, &sk->sk_socket->flags); + sk_wait_event(sk, &timeo, + !dn_queue_too_long(scp, queue, flags)); + clear_bit(SOCK_ASYNC_WAITDATA, &sk->sk_socket->flags); + finish_wait(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 char banner[] __initdata = KERN_INFO "NET4: DECnet for Linux: V.2.5.68s (C) 1995-2003 Linux DECnet Project Team\n"; + +static int __init decnet_init(void) +{ + int rc; + + printk(banner); + + rc = proto_register(&dn_proto, 1); + if (rc != 0) + goto out; + + dn_neigh_init(); + dn_dev_init(); + dn_route_init(); + dn_fib_init(); + + sock_register(&dn_family_ops); + dev_add_pack(&dn_dix_packet_type); + register_netdevice_notifier(&dn_dev_notifier); + + proc_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 diff --git a/net/decnet/dn_dev.c b/net/decnet/dn_dev.c new file mode 100644 index 000000000..b2c26b081 --- /dev/null +++ b/net/decnet/dn_dev.c @@ -0,0 +1,1446 @@ +/* + * DECnet An implementation of the DECnet protocol suite for the LINUX + * operating system. DECnet is implemented using the BSD Socket + * interface as the means of communication with the user level. + * + * DECnet Device Layer + * + * Authors: Steve Whitehouse <SteveW@ACM.org> + * Eduardo Marcelo Serrat <emserrat@geocities.com> + * + * Changes: + * Steve Whitehouse : Devices now see incoming frames so they + * can mark on who it came from. + * Steve Whitehouse : Fixed bug in creating neighbours. Each neighbour + * can now have a device specific setup func. + * Steve Whitehouse : Added /proc/sys/net/decnet/conf/<dev>/ + * Steve Whitehouse : Fixed bug which sometimes killed timer + * Steve Whitehouse : Multiple ifaddr support + * Steve Whitehouse : SIOCGIFCONF is now a compile time option + * Steve Whitehouse : /proc/sys/net/decnet/conf/<sys>/forwarding + * Steve Whitehouse : Removed timer1 - it's a user space issue now + * Patrick Caulfield : Fixed router hello message format + * Steve Whitehouse : Got rid of constant sizes for blksize for + * devices. All mtu based now. + */ + +#include <linux/capability.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/net.h> +#include <linux/netdevice.h> +#include <linux/proc_fs.h> +#include <linux/seq_file.h> +#include <linux/timer.h> +#include <linux/string.h> +#include <linux/if_addr.h> +#include <linux/if_arp.h> +#include <linux/if_ether.h> +#include <linux/skbuff.h> +#include <linux/sysctl.h> +#include <linux/notifier.h> +#include <linux/slab.h> +#include <linux/jiffies.h> +#include <asm/uaccess.h> +#include <net/net_namespace.h> +#include <net/neighbour.h> +#include <net/dst.h> +#include <net/flow.h> +#include <net/fib_rules.h> +#include <net/netlink.h> +#include <net/dn.h> +#include <net/dn_dev.h> +#include <net/dn_route.h> +#include <net/dn_neigh.h> +#include <net/dn_fib.h> + +#define DN_IFREQ_SIZE (sizeof(struct ifreq) - sizeof(struct sockaddr) + sizeof(struct sockaddr_dn)) + +static char dn_rt_all_end_mcast[ETH_ALEN] = {0xAB,0x00,0x00,0x04,0x00,0x00}; +static char dn_rt_all_rt_mcast[ETH_ALEN] = {0xAB,0x00,0x00,0x03,0x00,0x00}; +static char dn_hiord[ETH_ALEN] = {0xAA,0x00,0x04,0x00,0x00,0x00}; +static unsigned char dn_eco_version[3] = {0x02,0x00,0x00}; + +extern struct neigh_table dn_neigh_table; + +/* + * decnet_address is kept in network order. + */ +__le16 decnet_address = 0; + +static DEFINE_SPINLOCK(dndev_lock); +static struct net_device *decnet_default_device; +static BLOCKING_NOTIFIER_HEAD(dnaddr_chain); + +static struct dn_dev *dn_dev_create(struct net_device *dev, int *err); +static void dn_dev_delete(struct net_device *dev); +static void dn_ifaddr_notify(int event, struct dn_ifaddr *ifa); + +static int dn_eth_up(struct net_device *); +static void dn_eth_down(struct net_device *); +static void dn_send_brd_hello(struct net_device *dev, struct dn_ifaddr *ifa); +static void dn_send_ptp_hello(struct net_device *dev, struct dn_ifaddr *ifa); + +static struct dn_dev_parms dn_dev_list[] = { +{ + .type = ARPHRD_ETHER, /* Ethernet */ + .mode = DN_DEV_BCAST, + .state = DN_DEV_S_RU, + .t2 = 1, + .t3 = 10, + .name = "ethernet", + .up = dn_eth_up, + .down = dn_eth_down, + .timer3 = dn_send_brd_hello, +}, +{ + .type = ARPHRD_IPGRE, /* DECnet tunneled over GRE in IP */ + .mode = DN_DEV_BCAST, + .state = DN_DEV_S_RU, + .t2 = 1, + .t3 = 10, + .name = "ipgre", + .timer3 = dn_send_brd_hello, +}, +#if 0 +{ + .type = ARPHRD_X25, /* Bog standard X.25 */ + .mode = DN_DEV_UCAST, + .state = DN_DEV_S_DS, + .t2 = 1, + .t3 = 120, + .name = "x25", + .timer3 = dn_send_ptp_hello, +}, +#endif +#if 0 +{ + .type = ARPHRD_PPP, /* DECnet over PPP */ + .mode = DN_DEV_BCAST, + .state = DN_DEV_S_RU, + .t2 = 1, + .t3 = 10, + .name = "ppp", + .timer3 = dn_send_brd_hello, +}, +#endif +{ + .type = ARPHRD_DDCMP, /* DECnet over DDCMP */ + .mode = DN_DEV_UCAST, + .state = DN_DEV_S_DS, + .t2 = 1, + .t3 = 120, + .name = "ddcmp", + .timer3 = dn_send_ptp_hello, +}, +{ + .type = ARPHRD_LOOPBACK, /* Loopback interface - always last */ + .mode = DN_DEV_BCAST, + .state = DN_DEV_S_RU, + .t2 = 1, + .t3 = 10, + .name = "loopback", + .timer3 = dn_send_brd_hello, +} +}; + +#define DN_DEV_LIST_SIZE ARRAY_SIZE(dn_dev_list) + +#define DN_DEV_PARMS_OFFSET(x) offsetof(struct dn_dev_parms, x) + +#ifdef CONFIG_SYSCTL + +static int min_t2[] = { 1 }; +static int max_t2[] = { 60 }; /* No max specified, but this seems sensible */ +static int min_t3[] = { 1 }; +static int max_t3[] = { 8191 }; /* Must fit in 16 bits when multiplied by BCT3MULT or T3MULT */ + +static int min_priority[1]; +static int max_priority[] = { 127 }; /* From DECnet spec */ + +static int dn_forwarding_proc(struct ctl_table *, int, + void __user *, size_t *, loff_t *); +static struct dn_dev_sysctl_table { + struct ctl_table_header *sysctl_header; + struct ctl_table dn_dev_vars[5]; +} dn_dev_sysctl = { + NULL, + { + { + .procname = "forwarding", + .data = (void *)DN_DEV_PARMS_OFFSET(forwarding), + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = dn_forwarding_proc, + }, + { + .procname = "priority", + .data = (void *)DN_DEV_PARMS_OFFSET(priority), + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec_minmax, + .extra1 = &min_priority, + .extra2 = &max_priority + }, + { + .procname = "t2", + .data = (void *)DN_DEV_PARMS_OFFSET(t2), + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec_minmax, + .extra1 = &min_t2, + .extra2 = &max_t2 + }, + { + .procname = "t3", + .data = (void *)DN_DEV_PARMS_OFFSET(t3), + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec_minmax, + .extra1 = &min_t3, + .extra2 = &max_t3 + }, + {0} + }, +}; + +static void dn_dev_sysctl_register(struct net_device *dev, struct dn_dev_parms *parms) +{ + struct dn_dev_sysctl_table *t; + int i; + + char path[sizeof("net/decnet/conf/") + IFNAMSIZ]; + + t = kmemdup(&dn_dev_sysctl, sizeof(*t), GFP_KERNEL); + if (t == NULL) + return; + + for(i = 0; i < ARRAY_SIZE(t->dn_dev_vars) - 1; i++) { + long offset = (long)t->dn_dev_vars[i].data; + t->dn_dev_vars[i].data = ((char *)parms) + offset; + } + + snprintf(path, sizeof(path), "net/decnet/conf/%s", + dev? dev->name : parms->name); + + t->dn_dev_vars[0].extra1 = (void *)dev; + + t->sysctl_header = register_net_sysctl(&init_net, path, t->dn_dev_vars); + if (t->sysctl_header == NULL) + kfree(t); + else + parms->sysctl = t; +} + +static void dn_dev_sysctl_unregister(struct dn_dev_parms *parms) +{ + if (parms->sysctl) { + struct dn_dev_sysctl_table *t = parms->sysctl; + parms->sysctl = NULL; + unregister_net_sysctl_table(t->sysctl_header); + kfree(t); + } +} + +static int dn_forwarding_proc(struct ctl_table *table, int write, + void __user *buffer, + size_t *lenp, loff_t *ppos) +{ +#ifdef CONFIG_DECNET_ROUTER + struct net_device *dev = table->extra1; + struct dn_dev *dn_db; + int err; + int tmp, old; + + if (table->extra1 == NULL) + return -EINVAL; + + dn_db = rcu_dereference_raw(dev->dn_ptr); + old = dn_db->parms.forwarding; + + err = proc_dointvec(table, write, buffer, lenp, ppos); + + if ((err >= 0) && write) { + if (dn_db->parms.forwarding < 0) + dn_db->parms.forwarding = 0; + if (dn_db->parms.forwarding > 2) + dn_db->parms.forwarding = 2; + /* + * What an ugly hack this is... its works, just. It + * would be nice if sysctl/proc were just that little + * bit more flexible so I don't have to write a special + * routine, or suffer hacks like this - SJW + */ + tmp = dn_db->parms.forwarding; + dn_db->parms.forwarding = old; + if (dn_db->parms.down) + dn_db->parms.down(dev); + dn_db->parms.forwarding = tmp; + if (dn_db->parms.up) + dn_db->parms.up(dev); + } + + return err; +#else + return -EINVAL; +#endif +} + +#else /* CONFIG_SYSCTL */ +static void dn_dev_sysctl_unregister(struct dn_dev_parms *parms) +{ +} +static void dn_dev_sysctl_register(struct net_device *dev, struct dn_dev_parms *parms) +{ +} + +#endif /* CONFIG_SYSCTL */ + +static inline __u16 mtu2blksize(struct net_device *dev) +{ + u32 blksize = dev->mtu; + if (blksize > 0xffff) + blksize = 0xffff; + + if (dev->type == ARPHRD_ETHER || + dev->type == ARPHRD_PPP || + dev->type == ARPHRD_IPGRE || + dev->type == ARPHRD_LOOPBACK) + blksize -= 2; + + return (__u16)blksize; +} + +static struct dn_ifaddr *dn_dev_alloc_ifa(void) +{ + struct dn_ifaddr *ifa; + + ifa = kzalloc(sizeof(*ifa), GFP_KERNEL); + + return ifa; +} + +static void dn_dev_free_ifa(struct dn_ifaddr *ifa) +{ + kfree_rcu(ifa, rcu); +} + +static void dn_dev_del_ifa(struct dn_dev *dn_db, struct dn_ifaddr __rcu **ifap, int destroy) +{ + struct dn_ifaddr *ifa1 = rtnl_dereference(*ifap); + unsigned char mac_addr[6]; + struct net_device *dev = dn_db->dev; + + ASSERT_RTNL(); + + *ifap = ifa1->ifa_next; + + if (dn_db->dev->type == ARPHRD_ETHER) { + if (ifa1->ifa_local != dn_eth2dn(dev->dev_addr)) { + dn_dn2eth(mac_addr, ifa1->ifa_local); + dev_mc_del(dev, mac_addr); + } + } + + dn_ifaddr_notify(RTM_DELADDR, ifa1); + blocking_notifier_call_chain(&dnaddr_chain, NETDEV_DOWN, ifa1); + if (destroy) { + dn_dev_free_ifa(ifa1); + + if (dn_db->ifa_list == NULL) + dn_dev_delete(dn_db->dev); + } +} + +static int dn_dev_insert_ifa(struct dn_dev *dn_db, struct dn_ifaddr *ifa) +{ + struct net_device *dev = dn_db->dev; + struct dn_ifaddr *ifa1; + unsigned char mac_addr[6]; + + ASSERT_RTNL(); + + /* Check for duplicates */ + for (ifa1 = rtnl_dereference(dn_db->ifa_list); + ifa1 != NULL; + ifa1 = rtnl_dereference(ifa1->ifa_next)) { + if (ifa1->ifa_local == ifa->ifa_local) + return -EEXIST; + } + + if (dev->type == ARPHRD_ETHER) { + if (ifa->ifa_local != dn_eth2dn(dev->dev_addr)) { + dn_dn2eth(mac_addr, ifa->ifa_local); + dev_mc_add(dev, mac_addr); + } + } + + ifa->ifa_next = dn_db->ifa_list; + rcu_assign_pointer(dn_db->ifa_list, ifa); + + dn_ifaddr_notify(RTM_NEWADDR, ifa); + blocking_notifier_call_chain(&dnaddr_chain, NETDEV_UP, ifa); + + return 0; +} + +static int dn_dev_set_ifa(struct net_device *dev, struct dn_ifaddr *ifa) +{ + struct dn_dev *dn_db = rtnl_dereference(dev->dn_ptr); + int rv; + + if (dn_db == NULL) { + int err; + dn_db = dn_dev_create(dev, &err); + if (dn_db == NULL) + return err; + } + + ifa->ifa_dev = dn_db; + + if (dev->flags & IFF_LOOPBACK) + ifa->ifa_scope = RT_SCOPE_HOST; + + rv = dn_dev_insert_ifa(dn_db, ifa); + if (rv) + dn_dev_free_ifa(ifa); + return rv; +} + + +int dn_dev_ioctl(unsigned int cmd, void __user *arg) +{ + char buffer[DN_IFREQ_SIZE]; + struct ifreq *ifr = (struct ifreq *)buffer; + struct sockaddr_dn *sdn = (struct sockaddr_dn *)&ifr->ifr_addr; + struct dn_dev *dn_db; + struct net_device *dev; + struct dn_ifaddr *ifa = NULL; + struct dn_ifaddr __rcu **ifap = NULL; + int ret = 0; + + if (copy_from_user(ifr, arg, DN_IFREQ_SIZE)) + return -EFAULT; + ifr->ifr_name[IFNAMSIZ-1] = 0; + + dev_load(&init_net, ifr->ifr_name); + + switch (cmd) { + case SIOCGIFADDR: + break; + case SIOCSIFADDR: + if (!capable(CAP_NET_ADMIN)) + return -EACCES; + if (sdn->sdn_family != AF_DECnet) + return -EINVAL; + break; + default: + return -EINVAL; + } + + rtnl_lock(); + + if ((dev = __dev_get_by_name(&init_net, ifr->ifr_name)) == NULL) { + ret = -ENODEV; + goto done; + } + + if ((dn_db = rtnl_dereference(dev->dn_ptr)) != NULL) { + for (ifap = &dn_db->ifa_list; + (ifa = rtnl_dereference(*ifap)) != NULL; + ifap = &ifa->ifa_next) + if (strcmp(ifr->ifr_name, ifa->ifa_label) == 0) + break; + } + + if (ifa == NULL && cmd != SIOCSIFADDR) { + ret = -EADDRNOTAVAIL; + goto done; + } + + switch (cmd) { + case SIOCGIFADDR: + *((__le16 *)sdn->sdn_nodeaddr) = ifa->ifa_local; + goto rarok; + + case SIOCSIFADDR: + if (!ifa) { + if ((ifa = dn_dev_alloc_ifa()) == NULL) { + ret = -ENOBUFS; + break; + } + memcpy(ifa->ifa_label, dev->name, IFNAMSIZ); + } else { + if (ifa->ifa_local == dn_saddr2dn(sdn)) + break; + dn_dev_del_ifa(dn_db, ifap, 0); + } + + ifa->ifa_local = ifa->ifa_address = dn_saddr2dn(sdn); + + ret = dn_dev_set_ifa(dev, ifa); + } +done: + rtnl_unlock(); + + return ret; +rarok: + if (copy_to_user(arg, ifr, DN_IFREQ_SIZE)) + ret = -EFAULT; + goto done; +} + +struct net_device *dn_dev_get_default(void) +{ + struct net_device *dev; + + spin_lock(&dndev_lock); + dev = decnet_default_device; + if (dev) { + if (dev->dn_ptr) + dev_hold(dev); + else + dev = NULL; + } + spin_unlock(&dndev_lock); + + return dev; +} + +int dn_dev_set_default(struct net_device *dev, int force) +{ + struct net_device *old = NULL; + int rv = -EBUSY; + if (!dev->dn_ptr) + return -ENODEV; + + spin_lock(&dndev_lock); + if (force || decnet_default_device == NULL) { + old = decnet_default_device; + decnet_default_device = dev; + rv = 0; + } + spin_unlock(&dndev_lock); + + if (old) + dev_put(old); + return rv; +} + +static void dn_dev_check_default(struct net_device *dev) +{ + spin_lock(&dndev_lock); + if (dev == decnet_default_device) { + decnet_default_device = NULL; + } else { + dev = NULL; + } + spin_unlock(&dndev_lock); + + if (dev) + dev_put(dev); +} + +/* + * Called with RTNL + */ +static struct dn_dev *dn_dev_by_index(int ifindex) +{ + struct net_device *dev; + struct dn_dev *dn_dev = NULL; + + dev = __dev_get_by_index(&init_net, ifindex); + if (dev) + dn_dev = rtnl_dereference(dev->dn_ptr); + + return dn_dev; +} + +static const struct nla_policy dn_ifa_policy[IFA_MAX+1] = { + [IFA_ADDRESS] = { .type = NLA_U16 }, + [IFA_LOCAL] = { .type = NLA_U16 }, + [IFA_LABEL] = { .type = NLA_STRING, + .len = IFNAMSIZ - 1 }, + [IFA_FLAGS] = { .type = NLA_U32 }, +}; + +static int dn_nl_deladdr(struct sk_buff *skb, struct nlmsghdr *nlh) +{ + struct net *net = sock_net(skb->sk); + struct nlattr *tb[IFA_MAX+1]; + struct dn_dev *dn_db; + struct ifaddrmsg *ifm; + struct dn_ifaddr *ifa; + struct dn_ifaddr __rcu **ifap; + int err = -EINVAL; + + if (!netlink_capable(skb, CAP_NET_ADMIN)) + return -EPERM; + + if (!net_eq(net, &init_net)) + goto errout; + + err = nlmsg_parse(nlh, sizeof(*ifm), tb, IFA_MAX, dn_ifa_policy); + if (err < 0) + goto errout; + + err = -ENODEV; + ifm = nlmsg_data(nlh); + if ((dn_db = dn_dev_by_index(ifm->ifa_index)) == NULL) + goto errout; + + err = -EADDRNOTAVAIL; + for (ifap = &dn_db->ifa_list; + (ifa = rtnl_dereference(*ifap)) != NULL; + ifap = &ifa->ifa_next) { + if (tb[IFA_LOCAL] && + nla_memcmp(tb[IFA_LOCAL], &ifa->ifa_local, 2)) + continue; + + if (tb[IFA_LABEL] && nla_strcmp(tb[IFA_LABEL], ifa->ifa_label)) + continue; + + dn_dev_del_ifa(dn_db, ifap, 1); + return 0; + } + +errout: + return err; +} + +static int dn_nl_newaddr(struct sk_buff *skb, struct nlmsghdr *nlh) +{ + struct net *net = sock_net(skb->sk); + struct nlattr *tb[IFA_MAX+1]; + struct net_device *dev; + struct dn_dev *dn_db; + struct ifaddrmsg *ifm; + struct dn_ifaddr *ifa; + int err; + + if (!netlink_capable(skb, CAP_NET_ADMIN)) + return -EPERM; + + if (!net_eq(net, &init_net)) + return -EINVAL; + + err = nlmsg_parse(nlh, sizeof(*ifm), tb, IFA_MAX, dn_ifa_policy); + if (err < 0) + return err; + + if (tb[IFA_LOCAL] == NULL) + return -EINVAL; + + ifm = nlmsg_data(nlh); + if ((dev = __dev_get_by_index(&init_net, ifm->ifa_index)) == NULL) + return -ENODEV; + + if ((dn_db = rtnl_dereference(dev->dn_ptr)) == NULL) { + dn_db = dn_dev_create(dev, &err); + if (!dn_db) + return err; + } + + if ((ifa = dn_dev_alloc_ifa()) == NULL) + return -ENOBUFS; + + if (tb[IFA_ADDRESS] == NULL) + tb[IFA_ADDRESS] = tb[IFA_LOCAL]; + + ifa->ifa_local = nla_get_le16(tb[IFA_LOCAL]); + ifa->ifa_address = nla_get_le16(tb[IFA_ADDRESS]); + ifa->ifa_flags = tb[IFA_FLAGS] ? nla_get_u32(tb[IFA_FLAGS]) : + ifm->ifa_flags; + ifa->ifa_scope = ifm->ifa_scope; + ifa->ifa_dev = dn_db; + + if (tb[IFA_LABEL]) + nla_strlcpy(ifa->ifa_label, tb[IFA_LABEL], IFNAMSIZ); + else + memcpy(ifa->ifa_label, dev->name, IFNAMSIZ); + + err = dn_dev_insert_ifa(dn_db, ifa); + if (err) + dn_dev_free_ifa(ifa); + + return err; +} + +static inline size_t dn_ifaddr_nlmsg_size(void) +{ + return NLMSG_ALIGN(sizeof(struct ifaddrmsg)) + + nla_total_size(IFNAMSIZ) /* IFA_LABEL */ + + nla_total_size(2) /* IFA_ADDRESS */ + + nla_total_size(2) /* IFA_LOCAL */ + + nla_total_size(4); /* IFA_FLAGS */ +} + +static int dn_nl_fill_ifaddr(struct sk_buff *skb, struct dn_ifaddr *ifa, + u32 portid, u32 seq, int event, unsigned int flags) +{ + struct ifaddrmsg *ifm; + struct nlmsghdr *nlh; + u32 ifa_flags = ifa->ifa_flags | IFA_F_PERMANENT; + + nlh = nlmsg_put(skb, portid, seq, event, sizeof(*ifm), flags); + if (nlh == NULL) + return -EMSGSIZE; + + ifm = nlmsg_data(nlh); + ifm->ifa_family = AF_DECnet; + ifm->ifa_prefixlen = 16; + ifm->ifa_flags = ifa_flags; + ifm->ifa_scope = ifa->ifa_scope; + ifm->ifa_index = ifa->ifa_dev->dev->ifindex; + + if ((ifa->ifa_address && + nla_put_le16(skb, IFA_ADDRESS, ifa->ifa_address)) || + (ifa->ifa_local && + nla_put_le16(skb, IFA_LOCAL, ifa->ifa_local)) || + (ifa->ifa_label[0] && + nla_put_string(skb, IFA_LABEL, ifa->ifa_label)) || + nla_put_u32(skb, IFA_FLAGS, ifa_flags)) + goto nla_put_failure; + nlmsg_end(skb, nlh); + return 0; + +nla_put_failure: + nlmsg_cancel(skb, nlh); + return -EMSGSIZE; +} + +static void dn_ifaddr_notify(int event, struct dn_ifaddr *ifa) +{ + struct sk_buff *skb; + int err = -ENOBUFS; + + skb = alloc_skb(dn_ifaddr_nlmsg_size(), GFP_KERNEL); + if (skb == NULL) + goto errout; + + err = dn_nl_fill_ifaddr(skb, ifa, 0, 0, event, 0); + if (err < 0) { + /* -EMSGSIZE implies BUG in dn_ifaddr_nlmsg_size() */ + WARN_ON(err == -EMSGSIZE); + kfree_skb(skb); + goto errout; + } + rtnl_notify(skb, &init_net, 0, RTNLGRP_DECnet_IFADDR, NULL, GFP_KERNEL); + return; +errout: + if (err < 0) + rtnl_set_sk_err(&init_net, RTNLGRP_DECnet_IFADDR, err); +} + +static int dn_nl_dump_ifaddr(struct sk_buff *skb, struct netlink_callback *cb) +{ + struct net *net = sock_net(skb->sk); + int idx, dn_idx = 0, skip_ndevs, skip_naddr; + struct net_device *dev; + struct dn_dev *dn_db; + struct dn_ifaddr *ifa; + + if (!net_eq(net, &init_net)) + return 0; + + skip_ndevs = cb->args[0]; + skip_naddr = cb->args[1]; + + idx = 0; + rcu_read_lock(); + for_each_netdev_rcu(&init_net, dev) { + if (idx < skip_ndevs) + goto cont; + else if (idx > skip_ndevs) { + /* Only skip over addresses for first dev dumped + * in this iteration (idx == skip_ndevs) */ + skip_naddr = 0; + } + + if ((dn_db = rcu_dereference(dev->dn_ptr)) == NULL) + goto cont; + + for (ifa = rcu_dereference(dn_db->ifa_list), dn_idx = 0; ifa; + ifa = rcu_dereference(ifa->ifa_next), dn_idx++) { + if (dn_idx < skip_naddr) + continue; + + if (dn_nl_fill_ifaddr(skb, ifa, NETLINK_CB(cb->skb).portid, + cb->nlh->nlmsg_seq, RTM_NEWADDR, + NLM_F_MULTI) < 0) + goto done; + } +cont: + idx++; + } +done: + rcu_read_unlock(); + cb->args[0] = idx; + cb->args[1] = dn_idx; + + return skb->len; +} + +static int dn_dev_get_first(struct net_device *dev, __le16 *addr) +{ + struct dn_dev *dn_db; + struct dn_ifaddr *ifa; + int rv = -ENODEV; + + rcu_read_lock(); + dn_db = rcu_dereference(dev->dn_ptr); + if (dn_db == NULL) + goto out; + + ifa = rcu_dereference(dn_db->ifa_list); + if (ifa != NULL) { + *addr = ifa->ifa_local; + rv = 0; + } +out: + rcu_read_unlock(); + return rv; +} + +/* + * Find a default address to bind to. + * + * This is one of those areas where the initial VMS concepts don't really + * map onto the Linux concepts, and since we introduced multiple addresses + * per interface we have to cope with slightly odd ways of finding out what + * "our address" really is. Mostly it's not a problem; for this we just guess + * a sensible default. Eventually the routing code will take care of all the + * nasties for us I hope. + */ +int dn_dev_bind_default(__le16 *addr) +{ + struct net_device *dev; + int rv; + dev = dn_dev_get_default(); +last_chance: + if (dev) { + rv = dn_dev_get_first(dev, addr); + dev_put(dev); + if (rv == 0 || dev == init_net.loopback_dev) + return rv; + } + dev = init_net.loopback_dev; + dev_hold(dev); + goto last_chance; +} + +static void dn_send_endnode_hello(struct net_device *dev, struct dn_ifaddr *ifa) +{ + struct endnode_hello_message *msg; + struct sk_buff *skb = NULL; + __le16 *pktlen; + struct dn_dev *dn_db = rcu_dereference_raw(dev->dn_ptr); + + if ((skb = dn_alloc_skb(NULL, sizeof(*msg), GFP_ATOMIC)) == NULL) + return; + + skb->dev = dev; + + msg = (struct endnode_hello_message *)skb_put(skb,sizeof(*msg)); + + msg->msgflg = 0x0D; + memcpy(msg->tiver, dn_eco_version, 3); + dn_dn2eth(msg->id, ifa->ifa_local); + msg->iinfo = DN_RT_INFO_ENDN; + msg->blksize = cpu_to_le16(mtu2blksize(dev)); + msg->area = 0x00; + memset(msg->seed, 0, 8); + memcpy(msg->neighbor, dn_hiord, ETH_ALEN); + + if (dn_db->router) { + struct dn_neigh *dn = (struct dn_neigh *)dn_db->router; + dn_dn2eth(msg->neighbor, dn->addr); + } + + msg->timer = cpu_to_le16((unsigned short)dn_db->parms.t3); + msg->mpd = 0x00; + msg->datalen = 0x02; + memset(msg->data, 0xAA, 2); + + pktlen = (__le16 *)skb_push(skb,2); + *pktlen = cpu_to_le16(skb->len - 2); + + skb_reset_network_header(skb); + + dn_rt_finish_output(skb, dn_rt_all_rt_mcast, msg->id); +} + + +#define DRDELAY (5 * HZ) + +static int dn_am_i_a_router(struct dn_neigh *dn, struct dn_dev *dn_db, struct dn_ifaddr *ifa) +{ + /* First check time since device went up */ + if (time_before(jiffies, dn_db->uptime + DRDELAY)) + return 0; + + /* If there is no router, then yes... */ + if (!dn_db->router) + return 1; + + /* otherwise only if we have a higher priority or.. */ + if (dn->priority < dn_db->parms.priority) + return 1; + + /* if we have equal priority and a higher node number */ + if (dn->priority != dn_db->parms.priority) + return 0; + + if (le16_to_cpu(dn->addr) < le16_to_cpu(ifa->ifa_local)) + return 1; + + return 0; +} + +static void dn_send_router_hello(struct net_device *dev, struct dn_ifaddr *ifa) +{ + int n; + struct dn_dev *dn_db = rcu_dereference_raw(dev->dn_ptr); + struct dn_neigh *dn = (struct dn_neigh *)dn_db->router; + struct sk_buff *skb; + size_t size; + unsigned char *ptr; + unsigned char *i1, *i2; + __le16 *pktlen; + char *src; + + if (mtu2blksize(dev) < (26 + 7)) + return; + + n = mtu2blksize(dev) - 26; + n /= 7; + + if (n > 32) + n = 32; + + size = 2 + 26 + 7 * n; + + if ((skb = dn_alloc_skb(NULL, size, GFP_ATOMIC)) == NULL) + return; + + skb->dev = dev; + ptr = skb_put(skb, size); + + *ptr++ = DN_RT_PKT_CNTL | DN_RT_PKT_ERTH; + *ptr++ = 2; /* ECO */ + *ptr++ = 0; + *ptr++ = 0; + dn_dn2eth(ptr, ifa->ifa_local); + src = ptr; + ptr += ETH_ALEN; + *ptr++ = dn_db->parms.forwarding == 1 ? + DN_RT_INFO_L1RT : DN_RT_INFO_L2RT; + *((__le16 *)ptr) = cpu_to_le16(mtu2blksize(dev)); + ptr += 2; + *ptr++ = dn_db->parms.priority; /* Priority */ + *ptr++ = 0; /* Area: Reserved */ + *((__le16 *)ptr) = cpu_to_le16((unsigned short)dn_db->parms.t3); + ptr += 2; + *ptr++ = 0; /* MPD: Reserved */ + i1 = ptr++; + memset(ptr, 0, 7); /* Name: Reserved */ + ptr += 7; + i2 = ptr++; + + n = dn_neigh_elist(dev, ptr, n); + + *i2 = 7 * n; + *i1 = 8 + *i2; + + skb_trim(skb, (27 + *i2)); + + pktlen = (__le16 *)skb_push(skb, 2); + *pktlen = cpu_to_le16(skb->len - 2); + + skb_reset_network_header(skb); + + if (dn_am_i_a_router(dn, dn_db, ifa)) { + struct sk_buff *skb2 = skb_copy(skb, GFP_ATOMIC); + if (skb2) { + dn_rt_finish_output(skb2, dn_rt_all_end_mcast, src); + } + } + + dn_rt_finish_output(skb, dn_rt_all_rt_mcast, src); +} + +static void dn_send_brd_hello(struct net_device *dev, struct dn_ifaddr *ifa) +{ + struct dn_dev *dn_db = rcu_dereference_raw(dev->dn_ptr); + + if (dn_db->parms.forwarding == 0) + dn_send_endnode_hello(dev, ifa); + else + dn_send_router_hello(dev, ifa); +} + +static void dn_send_ptp_hello(struct net_device *dev, struct dn_ifaddr *ifa) +{ + int tdlen = 16; + int size = dev->hard_header_len + 2 + 4 + tdlen; + struct sk_buff *skb = dn_alloc_skb(NULL, size, GFP_ATOMIC); + int i; + unsigned char *ptr; + char src[ETH_ALEN]; + + if (skb == NULL) + return ; + + skb->dev = dev; + skb_push(skb, dev->hard_header_len); + ptr = skb_put(skb, 2 + 4 + tdlen); + + *ptr++ = DN_RT_PKT_HELO; + *((__le16 *)ptr) = ifa->ifa_local; + ptr += 2; + *ptr++ = tdlen; + + for(i = 0; i < tdlen; i++) + *ptr++ = 0252; + + dn_dn2eth(src, ifa->ifa_local); + dn_rt_finish_output(skb, dn_rt_all_rt_mcast, src); +} + +static int dn_eth_up(struct net_device *dev) +{ + struct dn_dev *dn_db = rcu_dereference_raw(dev->dn_ptr); + + if (dn_db->parms.forwarding == 0) + dev_mc_add(dev, dn_rt_all_end_mcast); + else + dev_mc_add(dev, dn_rt_all_rt_mcast); + + dn_db->use_long = 1; + + return 0; +} + +static void dn_eth_down(struct net_device *dev) +{ + struct dn_dev *dn_db = rcu_dereference_raw(dev->dn_ptr); + + if (dn_db->parms.forwarding == 0) + dev_mc_del(dev, dn_rt_all_end_mcast); + else + dev_mc_del(dev, dn_rt_all_rt_mcast); +} + +static void dn_dev_set_timer(struct net_device *dev); + +static void dn_dev_timer_func(unsigned long arg) +{ + struct net_device *dev = (struct net_device *)arg; + struct dn_dev *dn_db; + struct dn_ifaddr *ifa; + + rcu_read_lock(); + dn_db = rcu_dereference(dev->dn_ptr); + if (dn_db->t3 <= dn_db->parms.t2) { + if (dn_db->parms.timer3) { + for (ifa = rcu_dereference(dn_db->ifa_list); + ifa; + ifa = rcu_dereference(ifa->ifa_next)) { + if (!(ifa->ifa_flags & IFA_F_SECONDARY)) + dn_db->parms.timer3(dev, ifa); + } + } + dn_db->t3 = dn_db->parms.t3; + } else { + dn_db->t3 -= dn_db->parms.t2; + } + rcu_read_unlock(); + dn_dev_set_timer(dev); +} + +static void dn_dev_set_timer(struct net_device *dev) +{ + struct dn_dev *dn_db = rcu_dereference_raw(dev->dn_ptr); + + if (dn_db->parms.t2 > dn_db->parms.t3) + dn_db->parms.t2 = dn_db->parms.t3; + + dn_db->timer.data = (unsigned long)dev; + dn_db->timer.function = dn_dev_timer_func; + dn_db->timer.expires = jiffies + (dn_db->parms.t2 * HZ); + + add_timer(&dn_db->timer); +} + +static struct dn_dev *dn_dev_create(struct net_device *dev, int *err) +{ + int i; + struct dn_dev_parms *p = dn_dev_list; + struct dn_dev *dn_db; + + for(i = 0; i < DN_DEV_LIST_SIZE; i++, p++) { + if (p->type == dev->type) + break; + } + + *err = -ENODEV; + if (i == DN_DEV_LIST_SIZE) + return NULL; + + *err = -ENOBUFS; + if ((dn_db = kzalloc(sizeof(struct dn_dev), GFP_ATOMIC)) == NULL) + return NULL; + + memcpy(&dn_db->parms, p, sizeof(struct dn_dev_parms)); + + rcu_assign_pointer(dev->dn_ptr, dn_db); + dn_db->dev = dev; + init_timer(&dn_db->timer); + + dn_db->uptime = jiffies; + + dn_db->neigh_parms = neigh_parms_alloc(dev, &dn_neigh_table); + if (!dn_db->neigh_parms) { + RCU_INIT_POINTER(dev->dn_ptr, NULL); + kfree(dn_db); + return NULL; + } + + if (dn_db->parms.up) { + if (dn_db->parms.up(dev) < 0) { + neigh_parms_release(&dn_neigh_table, dn_db->neigh_parms); + dev->dn_ptr = NULL; + kfree(dn_db); + return NULL; + } + } + + dn_dev_sysctl_register(dev, &dn_db->parms); + + dn_dev_set_timer(dev); + + *err = 0; + return dn_db; +} + + +/* + * This processes a device up event. We only start up + * the loopback device & ethernet devices with correct + * MAC addresses automatically. Others must be started + * specifically. + * + * FIXME: How should we configure the loopback address ? If we could dispense + * with using decnet_address here and for autobind, it will be one less thing + * for users to worry about setting up. + */ + +void dn_dev_up(struct net_device *dev) +{ + struct dn_ifaddr *ifa; + __le16 addr = decnet_address; + int maybe_default = 0; + struct dn_dev *dn_db = rtnl_dereference(dev->dn_ptr); + + if ((dev->type != ARPHRD_ETHER) && (dev->type != ARPHRD_LOOPBACK)) + return; + + /* + * Need to ensure that loopback device has a dn_db attached to it + * to allow creation of neighbours against it, even though it might + * not have a local address of its own. Might as well do the same for + * all autoconfigured interfaces. + */ + if (dn_db == NULL) { + int err; + dn_db = dn_dev_create(dev, &err); + if (dn_db == NULL) + return; + } + + if (dev->type == ARPHRD_ETHER) { + if (memcmp(dev->dev_addr, dn_hiord, 4) != 0) + return; + addr = dn_eth2dn(dev->dev_addr); + maybe_default = 1; + } + + if (addr == 0) + return; + + if ((ifa = dn_dev_alloc_ifa()) == NULL) + return; + + ifa->ifa_local = ifa->ifa_address = addr; + ifa->ifa_flags = 0; + ifa->ifa_scope = RT_SCOPE_UNIVERSE; + strcpy(ifa->ifa_label, dev->name); + + dn_dev_set_ifa(dev, ifa); + + /* + * Automagically set the default device to the first automatically + * configured ethernet card in the system. + */ + if (maybe_default) { + dev_hold(dev); + if (dn_dev_set_default(dev, 0)) + dev_put(dev); + } +} + +static void dn_dev_delete(struct net_device *dev) +{ + struct dn_dev *dn_db = rtnl_dereference(dev->dn_ptr); + + if (dn_db == NULL) + return; + + del_timer_sync(&dn_db->timer); + dn_dev_sysctl_unregister(&dn_db->parms); + dn_dev_check_default(dev); + neigh_ifdown(&dn_neigh_table, dev); + + if (dn_db->parms.down) + dn_db->parms.down(dev); + + dev->dn_ptr = NULL; + + neigh_parms_release(&dn_neigh_table, dn_db->neigh_parms); + neigh_ifdown(&dn_neigh_table, dev); + + if (dn_db->router) + neigh_release(dn_db->router); + if (dn_db->peer) + neigh_release(dn_db->peer); + + kfree(dn_db); +} + +void dn_dev_down(struct net_device *dev) +{ + struct dn_dev *dn_db = rtnl_dereference(dev->dn_ptr); + struct dn_ifaddr *ifa; + + if (dn_db == NULL) + return; + + while ((ifa = rtnl_dereference(dn_db->ifa_list)) != NULL) { + dn_dev_del_ifa(dn_db, &dn_db->ifa_list, 0); + dn_dev_free_ifa(ifa); + } + + dn_dev_delete(dev); +} + +void dn_dev_init_pkt(struct sk_buff *skb) +{ +} + +void dn_dev_veri_pkt(struct sk_buff *skb) +{ +} + +void dn_dev_hello(struct sk_buff *skb) +{ +} + +void dn_dev_devices_off(void) +{ + struct net_device *dev; + + rtnl_lock(); + for_each_netdev(&init_net, dev) + dn_dev_down(dev); + rtnl_unlock(); + +} + +void dn_dev_devices_on(void) +{ + struct net_device *dev; + + rtnl_lock(); + for_each_netdev(&init_net, dev) { + if (dev->flags & IFF_UP) + dn_dev_up(dev); + } + rtnl_unlock(); +} + +int register_dnaddr_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&dnaddr_chain, nb); +} + +int unregister_dnaddr_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&dnaddr_chain, nb); +} + +#ifdef CONFIG_PROC_FS +static inline int is_dn_dev(struct net_device *dev) +{ + return dev->dn_ptr != NULL; +} + +static void *dn_dev_seq_start(struct seq_file *seq, loff_t *pos) + __acquires(RCU) +{ + int i; + struct net_device *dev; + + rcu_read_lock(); + + if (*pos == 0) + return SEQ_START_TOKEN; + + i = 1; + for_each_netdev_rcu(&init_net, dev) { + if (!is_dn_dev(dev)) + continue; + + if (i++ == *pos) + return dev; + } + + return NULL; +} + +static void *dn_dev_seq_next(struct seq_file *seq, void *v, loff_t *pos) +{ + struct net_device *dev; + + ++*pos; + + dev = v; + if (v == SEQ_START_TOKEN) + dev = net_device_entry(&init_net.dev_base_head); + + for_each_netdev_continue_rcu(&init_net, dev) { + if (!is_dn_dev(dev)) + continue; + + return dev; + } + + return NULL; +} + +static void dn_dev_seq_stop(struct seq_file *seq, void *v) + __releases(RCU) +{ + rcu_read_unlock(); +} + +static char *dn_type2asc(char type) +{ + switch (type) { + case DN_DEV_BCAST: + return "B"; + case DN_DEV_UCAST: + return "U"; + case DN_DEV_MPOINT: + return "M"; + } + + return "?"; +} + +static int dn_dev_seq_show(struct seq_file *seq, void *v) +{ + if (v == SEQ_START_TOKEN) + seq_puts(seq, "Name Flags T1 Timer1 T3 Timer3 BlkSize Pri State DevType Router Peer\n"); + else { + struct net_device *dev = v; + char peer_buf[DN_ASCBUF_LEN]; + char router_buf[DN_ASCBUF_LEN]; + struct dn_dev *dn_db = rcu_dereference(dev->dn_ptr); + + seq_printf(seq, "%-8s %1s %04u %04u %04lu %04lu" + " %04hu %03d %02x %-10s %-7s %-7s\n", + dev->name ? dev->name : "???", + dn_type2asc(dn_db->parms.mode), + 0, 0, + dn_db->t3, dn_db->parms.t3, + mtu2blksize(dev), + dn_db->parms.priority, + dn_db->parms.state, dn_db->parms.name, + dn_db->router ? dn_addr2asc(le16_to_cpu(*(__le16 *)dn_db->router->primary_key), router_buf) : "", + dn_db->peer ? dn_addr2asc(le16_to_cpu(*(__le16 *)dn_db->peer->primary_key), peer_buf) : ""); + } + return 0; +} + +static const struct seq_operations dn_dev_seq_ops = { + .start = dn_dev_seq_start, + .next = dn_dev_seq_next, + .stop = dn_dev_seq_stop, + .show = dn_dev_seq_show, +}; + +static int dn_dev_seq_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &dn_dev_seq_ops); +} + +static const struct file_operations dn_dev_seq_fops = { + .owner = THIS_MODULE, + .open = dn_dev_seq_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +#endif /* CONFIG_PROC_FS */ + +static int addr[2]; +module_param_array(addr, int, NULL, 0444); +MODULE_PARM_DESC(addr, "The DECnet address of this machine: area,node"); + +void __init dn_dev_init(void) +{ + if (addr[0] > 63 || addr[0] < 0) { + printk(KERN_ERR "DECnet: Area must be between 0 and 63"); + return; + } + + if (addr[1] > 1023 || addr[1] < 0) { + printk(KERN_ERR "DECnet: Node must be between 0 and 1023"); + return; + } + + decnet_address = cpu_to_le16((addr[0] << 10) | addr[1]); + + dn_dev_devices_on(); + + rtnl_register(PF_DECnet, RTM_NEWADDR, dn_nl_newaddr, NULL, NULL); + rtnl_register(PF_DECnet, RTM_DELADDR, dn_nl_deladdr, NULL, NULL); + rtnl_register(PF_DECnet, RTM_GETADDR, NULL, dn_nl_dump_ifaddr, NULL); + + proc_create("decnet_dev", S_IRUGO, init_net.proc_net, &dn_dev_seq_fops); + +#ifdef CONFIG_SYSCTL + { + int i; + for(i = 0; i < DN_DEV_LIST_SIZE; i++) + dn_dev_sysctl_register(NULL, &dn_dev_list[i]); + } +#endif /* CONFIG_SYSCTL */ +} + +void __exit dn_dev_cleanup(void) +{ +#ifdef CONFIG_SYSCTL + { + int i; + for(i = 0; i < DN_DEV_LIST_SIZE; i++) + dn_dev_sysctl_unregister(&dn_dev_list[i]); + } +#endif /* CONFIG_SYSCTL */ + + remove_proc_entry("decnet_dev", init_net.proc_net); + + dn_dev_devices_off(); +} diff --git a/net/decnet/dn_fib.c b/net/decnet/dn_fib.c new file mode 100644 index 000000000..df4803437 --- /dev/null +++ b/net/decnet/dn_fib.c @@ -0,0 +1,791 @@ +/* + * DECnet An implementation of the DECnet protocol suite for the LINUX + * operating system. DECnet is implemented using the BSD Socket + * interface as the means of communication with the user level. + * + * DECnet Routing Forwarding Information Base (Glue/Info List) + * + * Author: Steve Whitehouse <SteveW@ACM.org> + * + * + * Changes: + * Alexey Kuznetsov : SMP locking changes + * Steve Whitehouse : Rewrote it... Well to be more correct, I + * copied most of it from the ipv4 fib code. + * Steve Whitehouse : Updated it in style and fixed a few bugs + * which were fixed in the ipv4 code since + * this code was copied from it. + * + */ +#include <linux/string.h> +#include <linux/net.h> +#include <linux/socket.h> +#include <linux/slab.h> +#include <linux/sockios.h> +#include <linux/init.h> +#include <linux/skbuff.h> +#include <linux/netlink.h> +#include <linux/rtnetlink.h> +#include <linux/proc_fs.h> +#include <linux/netdevice.h> +#include <linux/timer.h> +#include <linux/spinlock.h> +#include <linux/atomic.h> +#include <asm/uaccess.h> +#include <net/neighbour.h> +#include <net/dst.h> +#include <net/flow.h> +#include <net/fib_rules.h> +#include <net/dn.h> +#include <net/dn_route.h> +#include <net/dn_fib.h> +#include <net/dn_neigh.h> +#include <net/dn_dev.h> + +#define RT_MIN_TABLE 1 + +#define for_fib_info() { struct dn_fib_info *fi;\ + for(fi = dn_fib_info_list; fi; fi = fi->fib_next) +#define endfor_fib_info() } + +#define for_nexthops(fi) { int nhsel; const struct dn_fib_nh *nh;\ + for(nhsel = 0, nh = (fi)->fib_nh; nhsel < (fi)->fib_nhs; nh++, nhsel++) + +#define change_nexthops(fi) { int nhsel; struct dn_fib_nh *nh;\ + for(nhsel = 0, nh = (struct dn_fib_nh *)((fi)->fib_nh); nhsel < (fi)->fib_nhs; nh++, nhsel++) + +#define endfor_nexthops(fi) } + +static DEFINE_SPINLOCK(dn_fib_multipath_lock); +static struct dn_fib_info *dn_fib_info_list; +static DEFINE_SPINLOCK(dn_fib_info_lock); + +static struct +{ + int error; + u8 scope; +} dn_fib_props[RTN_MAX+1] = { + [RTN_UNSPEC] = { .error = 0, .scope = RT_SCOPE_NOWHERE }, + [RTN_UNICAST] = { .error = 0, .scope = RT_SCOPE_UNIVERSE }, + [RTN_LOCAL] = { .error = 0, .scope = RT_SCOPE_HOST }, + [RTN_BROADCAST] = { .error = -EINVAL, .scope = RT_SCOPE_NOWHERE }, + [RTN_ANYCAST] = { .error = -EINVAL, .scope = RT_SCOPE_NOWHERE }, + [RTN_MULTICAST] = { .error = -EINVAL, .scope = RT_SCOPE_NOWHERE }, + [RTN_BLACKHOLE] = { .error = -EINVAL, .scope = RT_SCOPE_UNIVERSE }, + [RTN_UNREACHABLE] = { .error = -EHOSTUNREACH, .scope = RT_SCOPE_UNIVERSE }, + [RTN_PROHIBIT] = { .error = -EACCES, .scope = RT_SCOPE_UNIVERSE }, + [RTN_THROW] = { .error = -EAGAIN, .scope = RT_SCOPE_UNIVERSE }, + [RTN_NAT] = { .error = 0, .scope = RT_SCOPE_NOWHERE }, + [RTN_XRESOLVE] = { .error = -EINVAL, .scope = RT_SCOPE_NOWHERE }, +}; + +static int dn_fib_sync_down(__le16 local, struct net_device *dev, int force); +static int dn_fib_sync_up(struct net_device *dev); + +void dn_fib_free_info(struct dn_fib_info *fi) +{ + if (fi->fib_dead == 0) { + printk(KERN_DEBUG "DECnet: BUG! Attempt to free alive dn_fib_info\n"); + return; + } + + change_nexthops(fi) { + if (nh->nh_dev) + dev_put(nh->nh_dev); + nh->nh_dev = NULL; + } endfor_nexthops(fi); + kfree(fi); +} + +void dn_fib_release_info(struct dn_fib_info *fi) +{ + spin_lock(&dn_fib_info_lock); + if (fi && --fi->fib_treeref == 0) { + if (fi->fib_next) + fi->fib_next->fib_prev = fi->fib_prev; + if (fi->fib_prev) + fi->fib_prev->fib_next = fi->fib_next; + if (fi == dn_fib_info_list) + dn_fib_info_list = fi->fib_next; + fi->fib_dead = 1; + dn_fib_info_put(fi); + } + spin_unlock(&dn_fib_info_lock); +} + +static inline int dn_fib_nh_comp(const struct dn_fib_info *fi, const struct dn_fib_info *ofi) +{ + const struct dn_fib_nh *onh = ofi->fib_nh; + + for_nexthops(fi) { + if (nh->nh_oif != onh->nh_oif || + nh->nh_gw != onh->nh_gw || + nh->nh_scope != onh->nh_scope || + nh->nh_weight != onh->nh_weight || + ((nh->nh_flags^onh->nh_flags)&~RTNH_F_DEAD)) + return -1; + onh++; + } endfor_nexthops(fi); + return 0; +} + +static inline struct dn_fib_info *dn_fib_find_info(const struct dn_fib_info *nfi) +{ + for_fib_info() { + if (fi->fib_nhs != nfi->fib_nhs) + continue; + if (nfi->fib_protocol == fi->fib_protocol && + nfi->fib_prefsrc == fi->fib_prefsrc && + nfi->fib_priority == fi->fib_priority && + memcmp(nfi->fib_metrics, fi->fib_metrics, sizeof(fi->fib_metrics)) == 0 && + ((nfi->fib_flags^fi->fib_flags)&~RTNH_F_DEAD) == 0 && + (nfi->fib_nhs == 0 || dn_fib_nh_comp(fi, nfi) == 0)) + return fi; + } endfor_fib_info(); + return NULL; +} + +static int dn_fib_count_nhs(const struct nlattr *attr) +{ + struct rtnexthop *nhp = nla_data(attr); + int nhs = 0, nhlen = nla_len(attr); + + while(nhlen >= (int)sizeof(struct rtnexthop)) { + if ((nhlen -= nhp->rtnh_len) < 0) + return 0; + nhs++; + nhp = RTNH_NEXT(nhp); + } + + return nhs; +} + +static int dn_fib_get_nhs(struct dn_fib_info *fi, const struct nlattr *attr, + const struct rtmsg *r) +{ + struct rtnexthop *nhp = nla_data(attr); + int nhlen = nla_len(attr); + + change_nexthops(fi) { + int attrlen = nhlen - sizeof(struct rtnexthop); + if (attrlen < 0 || (nhlen -= nhp->rtnh_len) < 0) + return -EINVAL; + + nh->nh_flags = (r->rtm_flags&~0xFF) | nhp->rtnh_flags; + nh->nh_oif = nhp->rtnh_ifindex; + nh->nh_weight = nhp->rtnh_hops + 1; + + if (attrlen) { + struct nlattr *gw_attr; + + gw_attr = nla_find((struct nlattr *) (nhp + 1), attrlen, RTA_GATEWAY); + nh->nh_gw = gw_attr ? nla_get_le16(gw_attr) : 0; + } + nhp = RTNH_NEXT(nhp); + } endfor_nexthops(fi); + + return 0; +} + + +static int dn_fib_check_nh(const struct rtmsg *r, struct dn_fib_info *fi, struct dn_fib_nh *nh) +{ + int err; + + if (nh->nh_gw) { + struct flowidn fld; + struct dn_fib_res res; + + if (nh->nh_flags&RTNH_F_ONLINK) { + struct net_device *dev; + + if (r->rtm_scope >= RT_SCOPE_LINK) + return -EINVAL; + if (dnet_addr_type(nh->nh_gw) != RTN_UNICAST) + return -EINVAL; + if ((dev = __dev_get_by_index(&init_net, nh->nh_oif)) == NULL) + return -ENODEV; + if (!(dev->flags&IFF_UP)) + return -ENETDOWN; + nh->nh_dev = dev; + dev_hold(dev); + nh->nh_scope = RT_SCOPE_LINK; + return 0; + } + + memset(&fld, 0, sizeof(fld)); + fld.daddr = nh->nh_gw; + fld.flowidn_oif = nh->nh_oif; + fld.flowidn_scope = r->rtm_scope + 1; + + if (fld.flowidn_scope < RT_SCOPE_LINK) + fld.flowidn_scope = RT_SCOPE_LINK; + + if ((err = dn_fib_lookup(&fld, &res)) != 0) + return err; + + err = -EINVAL; + if (res.type != RTN_UNICAST && res.type != RTN_LOCAL) + goto out; + nh->nh_scope = res.scope; + nh->nh_oif = DN_FIB_RES_OIF(res); + nh->nh_dev = DN_FIB_RES_DEV(res); + if (nh->nh_dev == NULL) + goto out; + dev_hold(nh->nh_dev); + err = -ENETDOWN; + if (!(nh->nh_dev->flags & IFF_UP)) + goto out; + err = 0; +out: + dn_fib_res_put(&res); + return err; + } else { + struct net_device *dev; + + if (nh->nh_flags&(RTNH_F_PERVASIVE|RTNH_F_ONLINK)) + return -EINVAL; + + dev = __dev_get_by_index(&init_net, nh->nh_oif); + if (dev == NULL || dev->dn_ptr == NULL) + return -ENODEV; + if (!(dev->flags&IFF_UP)) + return -ENETDOWN; + nh->nh_dev = dev; + dev_hold(nh->nh_dev); + nh->nh_scope = RT_SCOPE_HOST; + } + + return 0; +} + + +struct dn_fib_info *dn_fib_create_info(const struct rtmsg *r, struct nlattr *attrs[], + const struct nlmsghdr *nlh, int *errp) +{ + int err; + struct dn_fib_info *fi = NULL; + struct dn_fib_info *ofi; + int nhs = 1; + + if (r->rtm_type > RTN_MAX) + goto err_inval; + + if (dn_fib_props[r->rtm_type].scope > r->rtm_scope) + goto err_inval; + + if (attrs[RTA_MULTIPATH] && + (nhs = dn_fib_count_nhs(attrs[RTA_MULTIPATH])) == 0) + goto err_inval; + + fi = kzalloc(sizeof(*fi)+nhs*sizeof(struct dn_fib_nh), GFP_KERNEL); + err = -ENOBUFS; + if (fi == NULL) + goto failure; + + fi->fib_protocol = r->rtm_protocol; + fi->fib_nhs = nhs; + fi->fib_flags = r->rtm_flags; + + if (attrs[RTA_PRIORITY]) + fi->fib_priority = nla_get_u32(attrs[RTA_PRIORITY]); + + if (attrs[RTA_METRICS]) { + struct nlattr *attr; + int rem; + + nla_for_each_nested(attr, attrs[RTA_METRICS], rem) { + int type = nla_type(attr); + + if (type) { + if (type > RTAX_MAX || type == RTAX_CC_ALGO || + nla_len(attr) < 4) + goto err_inval; + + fi->fib_metrics[type-1] = nla_get_u32(attr); + } + } + } + + if (attrs[RTA_PREFSRC]) + fi->fib_prefsrc = nla_get_le16(attrs[RTA_PREFSRC]); + + if (attrs[RTA_MULTIPATH]) { + if ((err = dn_fib_get_nhs(fi, attrs[RTA_MULTIPATH], r)) != 0) + goto failure; + + if (attrs[RTA_OIF] && + fi->fib_nh->nh_oif != nla_get_u32(attrs[RTA_OIF])) + goto err_inval; + + if (attrs[RTA_GATEWAY] && + fi->fib_nh->nh_gw != nla_get_le16(attrs[RTA_GATEWAY])) + goto err_inval; + } else { + struct dn_fib_nh *nh = fi->fib_nh; + + if (attrs[RTA_OIF]) + nh->nh_oif = nla_get_u32(attrs[RTA_OIF]); + + if (attrs[RTA_GATEWAY]) + nh->nh_gw = nla_get_le16(attrs[RTA_GATEWAY]); + + nh->nh_flags = r->rtm_flags; + nh->nh_weight = 1; + } + + if (r->rtm_type == RTN_NAT) { + if (!attrs[RTA_GATEWAY] || nhs != 1 || attrs[RTA_OIF]) + goto err_inval; + + fi->fib_nh->nh_gw = nla_get_le16(attrs[RTA_GATEWAY]); + goto link_it; + } + + if (dn_fib_props[r->rtm_type].error) { + if (attrs[RTA_GATEWAY] || attrs[RTA_OIF] || attrs[RTA_MULTIPATH]) + goto err_inval; + + goto link_it; + } + + if (r->rtm_scope > RT_SCOPE_HOST) + goto err_inval; + + if (r->rtm_scope == RT_SCOPE_HOST) { + struct dn_fib_nh *nh = fi->fib_nh; + + /* Local address is added */ + if (nhs != 1 || nh->nh_gw) + goto err_inval; + nh->nh_scope = RT_SCOPE_NOWHERE; + nh->nh_dev = dev_get_by_index(&init_net, fi->fib_nh->nh_oif); + err = -ENODEV; + if (nh->nh_dev == NULL) + goto failure; + } else { + change_nexthops(fi) { + if ((err = dn_fib_check_nh(r, fi, nh)) != 0) + goto failure; + } endfor_nexthops(fi) + } + + if (fi->fib_prefsrc) { + if (r->rtm_type != RTN_LOCAL || !attrs[RTA_DST] || + fi->fib_prefsrc != nla_get_le16(attrs[RTA_DST])) + if (dnet_addr_type(fi->fib_prefsrc) != RTN_LOCAL) + goto err_inval; + } + +link_it: + if ((ofi = dn_fib_find_info(fi)) != NULL) { + fi->fib_dead = 1; + dn_fib_free_info(fi); + ofi->fib_treeref++; + return ofi; + } + + fi->fib_treeref++; + atomic_inc(&fi->fib_clntref); + spin_lock(&dn_fib_info_lock); + fi->fib_next = dn_fib_info_list; + fi->fib_prev = NULL; + if (dn_fib_info_list) + dn_fib_info_list->fib_prev = fi; + dn_fib_info_list = fi; + spin_unlock(&dn_fib_info_lock); + return fi; + +err_inval: + err = -EINVAL; + +failure: + *errp = err; + if (fi) { + fi->fib_dead = 1; + dn_fib_free_info(fi); + } + + return NULL; +} + +int dn_fib_semantic_match(int type, struct dn_fib_info *fi, const struct flowidn *fld, struct dn_fib_res *res) +{ + int err = dn_fib_props[type].error; + + if (err == 0) { + if (fi->fib_flags & RTNH_F_DEAD) + return 1; + + res->fi = fi; + + switch (type) { + case RTN_NAT: + DN_FIB_RES_RESET(*res); + atomic_inc(&fi->fib_clntref); + return 0; + case RTN_UNICAST: + case RTN_LOCAL: + for_nexthops(fi) { + if (nh->nh_flags & RTNH_F_DEAD) + continue; + if (!fld->flowidn_oif || + fld->flowidn_oif == nh->nh_oif) + break; + } + if (nhsel < fi->fib_nhs) { + res->nh_sel = nhsel; + atomic_inc(&fi->fib_clntref); + return 0; + } + endfor_nexthops(fi); + res->fi = NULL; + return 1; + default: + net_err_ratelimited("DECnet: impossible routing event : dn_fib_semantic_match type=%d\n", + type); + res->fi = NULL; + return -EINVAL; + } + } + return err; +} + +void dn_fib_select_multipath(const struct flowidn *fld, struct dn_fib_res *res) +{ + struct dn_fib_info *fi = res->fi; + int w; + + spin_lock_bh(&dn_fib_multipath_lock); + if (fi->fib_power <= 0) { + int power = 0; + change_nexthops(fi) { + if (!(nh->nh_flags&RTNH_F_DEAD)) { + power += nh->nh_weight; + nh->nh_power = nh->nh_weight; + } + } endfor_nexthops(fi); + fi->fib_power = power; + if (power < 0) { + spin_unlock_bh(&dn_fib_multipath_lock); + res->nh_sel = 0; + return; + } + } + + w = jiffies % fi->fib_power; + + change_nexthops(fi) { + if (!(nh->nh_flags&RTNH_F_DEAD) && nh->nh_power) { + if ((w -= nh->nh_power) <= 0) { + nh->nh_power--; + fi->fib_power--; + res->nh_sel = nhsel; + spin_unlock_bh(&dn_fib_multipath_lock); + return; + } + } + } endfor_nexthops(fi); + res->nh_sel = 0; + spin_unlock_bh(&dn_fib_multipath_lock); +} + +static inline u32 rtm_get_table(struct nlattr *attrs[], u8 table) +{ + if (attrs[RTA_TABLE]) + table = nla_get_u32(attrs[RTA_TABLE]); + + return table; +} + +static int dn_fib_rtm_delroute(struct sk_buff *skb, struct nlmsghdr *nlh) +{ + struct net *net = sock_net(skb->sk); + struct dn_fib_table *tb; + struct rtmsg *r = nlmsg_data(nlh); + struct nlattr *attrs[RTA_MAX+1]; + int err; + + if (!netlink_capable(skb, CAP_NET_ADMIN)) + return -EPERM; + + if (!net_eq(net, &init_net)) + return -EINVAL; + + err = nlmsg_parse(nlh, sizeof(*r), attrs, RTA_MAX, rtm_dn_policy); + if (err < 0) + return err; + + tb = dn_fib_get_table(rtm_get_table(attrs, r->rtm_table), 0); + if (!tb) + return -ESRCH; + + return tb->delete(tb, r, attrs, nlh, &NETLINK_CB(skb)); +} + +static int dn_fib_rtm_newroute(struct sk_buff *skb, struct nlmsghdr *nlh) +{ + struct net *net = sock_net(skb->sk); + struct dn_fib_table *tb; + struct rtmsg *r = nlmsg_data(nlh); + struct nlattr *attrs[RTA_MAX+1]; + int err; + + if (!netlink_capable(skb, CAP_NET_ADMIN)) + return -EPERM; + + if (!net_eq(net, &init_net)) + return -EINVAL; + + err = nlmsg_parse(nlh, sizeof(*r), attrs, RTA_MAX, rtm_dn_policy); + if (err < 0) + return err; + + tb = dn_fib_get_table(rtm_get_table(attrs, r->rtm_table), 1); + if (!tb) + return -ENOBUFS; + + return tb->insert(tb, r, attrs, nlh, &NETLINK_CB(skb)); +} + +static void fib_magic(int cmd, int type, __le16 dst, int dst_len, struct dn_ifaddr *ifa) +{ + struct dn_fib_table *tb; + struct { + struct nlmsghdr nlh; + struct rtmsg rtm; + } req; + struct { + struct nlattr hdr; + __le16 dst; + } dst_attr = { + .dst = dst, + }; + struct { + struct nlattr hdr; + __le16 prefsrc; + } prefsrc_attr = { + .prefsrc = ifa->ifa_local, + }; + struct { + struct nlattr hdr; + u32 oif; + } oif_attr = { + .oif = ifa->ifa_dev->dev->ifindex, + }; + struct nlattr *attrs[RTA_MAX+1] = { + [RTA_DST] = (struct nlattr *) &dst_attr, + [RTA_PREFSRC] = (struct nlattr * ) &prefsrc_attr, + [RTA_OIF] = (struct nlattr *) &oif_attr, + }; + + memset(&req.rtm, 0, sizeof(req.rtm)); + + if (type == RTN_UNICAST) + tb = dn_fib_get_table(RT_MIN_TABLE, 1); + else + tb = dn_fib_get_table(RT_TABLE_LOCAL, 1); + + if (tb == NULL) + return; + + req.nlh.nlmsg_len = sizeof(req); + req.nlh.nlmsg_type = cmd; + req.nlh.nlmsg_flags = NLM_F_REQUEST|NLM_F_CREATE|NLM_F_APPEND; + req.nlh.nlmsg_pid = 0; + req.nlh.nlmsg_seq = 0; + + req.rtm.rtm_dst_len = dst_len; + req.rtm.rtm_table = tb->n; + req.rtm.rtm_protocol = RTPROT_KERNEL; + req.rtm.rtm_scope = (type != RTN_LOCAL ? RT_SCOPE_LINK : RT_SCOPE_HOST); + req.rtm.rtm_type = type; + + if (cmd == RTM_NEWROUTE) + tb->insert(tb, &req.rtm, attrs, &req.nlh, NULL); + else + tb->delete(tb, &req.rtm, attrs, &req.nlh, NULL); +} + +static void dn_fib_add_ifaddr(struct dn_ifaddr *ifa) +{ + + fib_magic(RTM_NEWROUTE, RTN_LOCAL, ifa->ifa_local, 16, ifa); + +#if 0 + if (!(dev->flags&IFF_UP)) + return; + /* In the future, we will want to add default routes here */ + +#endif +} + +static void dn_fib_del_ifaddr(struct dn_ifaddr *ifa) +{ + int found_it = 0; + struct net_device *dev; + struct dn_dev *dn_db; + struct dn_ifaddr *ifa2; + + ASSERT_RTNL(); + + /* Scan device list */ + rcu_read_lock(); + for_each_netdev_rcu(&init_net, dev) { + dn_db = rcu_dereference(dev->dn_ptr); + if (dn_db == NULL) + continue; + for (ifa2 = rcu_dereference(dn_db->ifa_list); + ifa2 != NULL; + ifa2 = rcu_dereference(ifa2->ifa_next)) { + if (ifa2->ifa_local == ifa->ifa_local) { + found_it = 1; + break; + } + } + } + rcu_read_unlock(); + + if (found_it == 0) { + fib_magic(RTM_DELROUTE, RTN_LOCAL, ifa->ifa_local, 16, ifa); + + if (dnet_addr_type(ifa->ifa_local) != RTN_LOCAL) { + if (dn_fib_sync_down(ifa->ifa_local, NULL, 0)) + dn_fib_flush(); + } + } +} + +static void dn_fib_disable_addr(struct net_device *dev, int force) +{ + if (dn_fib_sync_down(0, dev, force)) + dn_fib_flush(); + dn_rt_cache_flush(0); + neigh_ifdown(&dn_neigh_table, dev); +} + +static int dn_fib_dnaddr_event(struct notifier_block *this, unsigned long event, void *ptr) +{ + struct dn_ifaddr *ifa = (struct dn_ifaddr *)ptr; + + switch (event) { + case NETDEV_UP: + dn_fib_add_ifaddr(ifa); + dn_fib_sync_up(ifa->ifa_dev->dev); + dn_rt_cache_flush(-1); + break; + case NETDEV_DOWN: + dn_fib_del_ifaddr(ifa); + if (ifa->ifa_dev && ifa->ifa_dev->ifa_list == NULL) { + dn_fib_disable_addr(ifa->ifa_dev->dev, 1); + } else { + dn_rt_cache_flush(-1); + } + break; + } + return NOTIFY_DONE; +} + +static int dn_fib_sync_down(__le16 local, struct net_device *dev, int force) +{ + int ret = 0; + int scope = RT_SCOPE_NOWHERE; + + if (force) + scope = -1; + + for_fib_info() { + /* + * This makes no sense for DECnet.... we will almost + * certainly have more than one local address the same + * over all our interfaces. It needs thinking about + * some more. + */ + if (local && fi->fib_prefsrc == local) { + fi->fib_flags |= RTNH_F_DEAD; + ret++; + } else if (dev && fi->fib_nhs) { + int dead = 0; + + change_nexthops(fi) { + if (nh->nh_flags&RTNH_F_DEAD) + dead++; + else if (nh->nh_dev == dev && + nh->nh_scope != scope) { + spin_lock_bh(&dn_fib_multipath_lock); + nh->nh_flags |= RTNH_F_DEAD; + fi->fib_power -= nh->nh_power; + nh->nh_power = 0; + spin_unlock_bh(&dn_fib_multipath_lock); + dead++; + } + } endfor_nexthops(fi) + if (dead == fi->fib_nhs) { + fi->fib_flags |= RTNH_F_DEAD; + ret++; + } + } + } endfor_fib_info(); + return ret; +} + + +static int dn_fib_sync_up(struct net_device *dev) +{ + int ret = 0; + + if (!(dev->flags&IFF_UP)) + return 0; + + for_fib_info() { + int alive = 0; + + change_nexthops(fi) { + if (!(nh->nh_flags&RTNH_F_DEAD)) { + alive++; + continue; + } + if (nh->nh_dev == NULL || !(nh->nh_dev->flags&IFF_UP)) + continue; + if (nh->nh_dev != dev || dev->dn_ptr == NULL) + continue; + alive++; + spin_lock_bh(&dn_fib_multipath_lock); + nh->nh_power = 0; + nh->nh_flags &= ~RTNH_F_DEAD; + spin_unlock_bh(&dn_fib_multipath_lock); + } endfor_nexthops(fi); + + if (alive > 0) { + fi->fib_flags &= ~RTNH_F_DEAD; + ret++; + } + } endfor_fib_info(); + return ret; +} + +static struct notifier_block dn_fib_dnaddr_notifier = { + .notifier_call = dn_fib_dnaddr_event, +}; + +void __exit dn_fib_cleanup(void) +{ + dn_fib_table_cleanup(); + dn_fib_rules_cleanup(); + + unregister_dnaddr_notifier(&dn_fib_dnaddr_notifier); +} + + +void __init dn_fib_init(void) +{ + dn_fib_table_init(); + dn_fib_rules_init(); + + register_dnaddr_notifier(&dn_fib_dnaddr_notifier); + + rtnl_register(PF_DECnet, RTM_NEWROUTE, dn_fib_rtm_newroute, NULL, NULL); + rtnl_register(PF_DECnet, RTM_DELROUTE, dn_fib_rtm_delroute, NULL, NULL); +} + + diff --git a/net/decnet/dn_neigh.c b/net/decnet/dn_neigh.c new file mode 100644 index 000000000..4507b188f --- /dev/null +++ b/net/decnet/dn_neigh.c @@ -0,0 +1,616 @@ +/* + * DECnet An implementation of the DECnet protocol suite for the LINUX + * operating system. DECnet is implemented using the BSD Socket + * interface as the means of communication with the user level. + * + * DECnet Neighbour Functions (Adjacency Database and + * On-Ethernet Cache) + * + * Author: Steve Whitehouse <SteveW@ACM.org> + * + * + * Changes: + * Steve Whitehouse : Fixed router listing routine + * Steve Whitehouse : Added error_report functions + * Steve Whitehouse : Added default router detection + * Steve Whitehouse : Hop counts in outgoing messages + * Steve Whitehouse : Fixed src/dst in outgoing messages so + * forwarding now stands a good chance of + * working. + * Steve Whitehouse : Fixed neighbour states (for now anyway). + * Steve Whitehouse : Made error_report functions dummies. This + * is not the right place to return skbs. + * Steve Whitehouse : Convert to seq_file + * + */ + +#include <linux/net.h> +#include <linux/module.h> +#include <linux/socket.h> +#include <linux/if_arp.h> +#include <linux/slab.h> +#include <linux/if_ether.h> +#include <linux/init.h> +#include <linux/proc_fs.h> +#include <linux/string.h> +#include <linux/netfilter_decnet.h> +#include <linux/spinlock.h> +#include <linux/seq_file.h> +#include <linux/rcupdate.h> +#include <linux/jhash.h> +#include <linux/atomic.h> +#include <net/net_namespace.h> +#include <net/neighbour.h> +#include <net/dst.h> +#include <net/flow.h> +#include <net/dn.h> +#include <net/dn_dev.h> +#include <net/dn_neigh.h> +#include <net/dn_route.h> + +static int dn_neigh_construct(struct neighbour *); +static void dn_neigh_error_report(struct neighbour *, struct sk_buff *); +static int dn_neigh_output(struct neighbour *neigh, struct sk_buff *skb); + +/* + * Operations for adding the link layer header. + */ +static const struct neigh_ops dn_neigh_ops = { + .family = AF_DECnet, + .error_report = dn_neigh_error_report, + .output = dn_neigh_output, + .connected_output = dn_neigh_output, +}; + +static u32 dn_neigh_hash(const void *pkey, + const struct net_device *dev, + __u32 *hash_rnd) +{ + return jhash_2words(*(__u16 *)pkey, 0, hash_rnd[0]); +} + +static bool dn_key_eq(const struct neighbour *neigh, const void *pkey) +{ + return neigh_key_eq16(neigh, pkey); +} + +struct neigh_table dn_neigh_table = { + .family = PF_DECnet, + .entry_size = NEIGH_ENTRY_SIZE(sizeof(struct dn_neigh)), + .key_len = sizeof(__le16), + .protocol = cpu_to_be16(ETH_P_DNA_RT), + .hash = dn_neigh_hash, + .key_eq = dn_key_eq, + .constructor = dn_neigh_construct, + .id = "dn_neigh_cache", + .parms ={ + .tbl = &dn_neigh_table, + .reachable_time = 30 * HZ, + .data = { + [NEIGH_VAR_MCAST_PROBES] = 0, + [NEIGH_VAR_UCAST_PROBES] = 0, + [NEIGH_VAR_APP_PROBES] = 0, + [NEIGH_VAR_RETRANS_TIME] = 1 * HZ, + [NEIGH_VAR_BASE_REACHABLE_TIME] = 30 * HZ, + [NEIGH_VAR_DELAY_PROBE_TIME] = 5 * HZ, + [NEIGH_VAR_GC_STALETIME] = 60 * HZ, + [NEIGH_VAR_QUEUE_LEN_BYTES] = 64*1024, + [NEIGH_VAR_PROXY_QLEN] = 0, + [NEIGH_VAR_ANYCAST_DELAY] = 0, + [NEIGH_VAR_PROXY_DELAY] = 0, + [NEIGH_VAR_LOCKTIME] = 1 * HZ, + }, + }, + .gc_interval = 30 * HZ, + .gc_thresh1 = 128, + .gc_thresh2 = 512, + .gc_thresh3 = 1024, +}; + +static int dn_neigh_construct(struct neighbour *neigh) +{ + struct net_device *dev = neigh->dev; + struct dn_neigh *dn = (struct dn_neigh *)neigh; + struct dn_dev *dn_db; + struct neigh_parms *parms; + + rcu_read_lock(); + dn_db = rcu_dereference(dev->dn_ptr); + if (dn_db == NULL) { + rcu_read_unlock(); + return -EINVAL; + } + + parms = dn_db->neigh_parms; + if (!parms) { + rcu_read_unlock(); + return -EINVAL; + } + + __neigh_parms_put(neigh->parms); + neigh->parms = neigh_parms_clone(parms); + rcu_read_unlock(); + + neigh->ops = &dn_neigh_ops; + neigh->nud_state = NUD_NOARP; + neigh->output = neigh->ops->connected_output; + + if ((dev->type == ARPHRD_IPGRE) || (dev->flags & IFF_POINTOPOINT)) + memcpy(neigh->ha, dev->broadcast, dev->addr_len); + else if ((dev->type == ARPHRD_ETHER) || (dev->type == ARPHRD_LOOPBACK)) + dn_dn2eth(neigh->ha, dn->addr); + else { + net_dbg_ratelimited("Trying to create neigh for hw %d\n", + dev->type); + return -EINVAL; + } + + /* + * Make an estimate of the remote block size by assuming that its + * two less then the device mtu, which it true for ethernet (and + * other things which support long format headers) since there is + * an extra length field (of 16 bits) which isn't part of the + * ethernet headers and which the DECnet specs won't admit is part + * of the DECnet routing headers either. + * + * If we over estimate here its no big deal, the NSP negotiations + * will prevent us from sending packets which are too large for the + * remote node to handle. In any case this figure is normally updated + * by a hello message in most cases. + */ + dn->blksize = dev->mtu - 2; + + return 0; +} + +static void dn_neigh_error_report(struct neighbour *neigh, struct sk_buff *skb) +{ + printk(KERN_DEBUG "dn_neigh_error_report: called\n"); + kfree_skb(skb); +} + +static int dn_neigh_output(struct neighbour *neigh, struct sk_buff *skb) +{ + struct dst_entry *dst = skb_dst(skb); + struct dn_route *rt = (struct dn_route *)dst; + struct net_device *dev = neigh->dev; + char mac_addr[ETH_ALEN]; + unsigned int seq; + int err; + + dn_dn2eth(mac_addr, rt->rt_local_src); + do { + seq = read_seqbegin(&neigh->ha_lock); + err = dev_hard_header(skb, dev, ntohs(skb->protocol), + neigh->ha, mac_addr, skb->len); + } while (read_seqretry(&neigh->ha_lock, seq)); + + if (err >= 0) + err = dev_queue_xmit(skb); + else { + kfree_skb(skb); + err = -EINVAL; + } + return err; +} + +static int dn_neigh_output_packet(struct sock *sk, struct sk_buff *skb) +{ + struct dst_entry *dst = skb_dst(skb); + struct dn_route *rt = (struct dn_route *)dst; + struct neighbour *neigh = rt->n; + + return neigh->output(neigh, skb); +} + +/* + * For talking to broadcast devices: Ethernet & PPP + */ +static int dn_long_output(struct neighbour *neigh, struct sock *sk, + struct sk_buff *skb) +{ + struct net_device *dev = neigh->dev; + int headroom = dev->hard_header_len + sizeof(struct dn_long_packet) + 3; + unsigned char *data; + struct dn_long_packet *lp; + struct dn_skb_cb *cb = DN_SKB_CB(skb); + + + if (skb_headroom(skb) < headroom) { + struct sk_buff *skb2 = skb_realloc_headroom(skb, headroom); + if (skb2 == NULL) { + net_crit_ratelimited("dn_long_output: no memory\n"); + kfree_skb(skb); + return -ENOBUFS; + } + consume_skb(skb); + skb = skb2; + net_info_ratelimited("dn_long_output: Increasing headroom\n"); + } + + data = skb_push(skb, sizeof(struct dn_long_packet) + 3); + lp = (struct dn_long_packet *)(data+3); + + *((__le16 *)data) = cpu_to_le16(skb->len - 2); + *(data + 2) = 1 | DN_RT_F_PF; /* Padding */ + + lp->msgflg = DN_RT_PKT_LONG|(cb->rt_flags&(DN_RT_F_IE|DN_RT_F_RQR|DN_RT_F_RTS)); + lp->d_area = lp->d_subarea = 0; + dn_dn2eth(lp->d_id, cb->dst); + lp->s_area = lp->s_subarea = 0; + dn_dn2eth(lp->s_id, cb->src); + lp->nl2 = 0; + lp->visit_ct = cb->hops & 0x3f; + lp->s_class = 0; + lp->pt = 0; + + skb_reset_network_header(skb); + + return NF_HOOK(NFPROTO_DECNET, NF_DN_POST_ROUTING, sk, skb, + NULL, neigh->dev, dn_neigh_output_packet); +} + +/* + * For talking to pointopoint and multidrop devices: DDCMP and X.25 + */ +static int dn_short_output(struct neighbour *neigh, struct sock *sk, + struct sk_buff *skb) +{ + struct net_device *dev = neigh->dev; + int headroom = dev->hard_header_len + sizeof(struct dn_short_packet) + 2; + struct dn_short_packet *sp; + unsigned char *data; + struct dn_skb_cb *cb = DN_SKB_CB(skb); + + + if (skb_headroom(skb) < headroom) { + struct sk_buff *skb2 = skb_realloc_headroom(skb, headroom); + if (skb2 == NULL) { + net_crit_ratelimited("dn_short_output: no memory\n"); + kfree_skb(skb); + return -ENOBUFS; + } + consume_skb(skb); + skb = skb2; + net_info_ratelimited("dn_short_output: Increasing headroom\n"); + } + + data = skb_push(skb, sizeof(struct dn_short_packet) + 2); + *((__le16 *)data) = cpu_to_le16(skb->len - 2); + sp = (struct dn_short_packet *)(data+2); + + sp->msgflg = DN_RT_PKT_SHORT|(cb->rt_flags&(DN_RT_F_RQR|DN_RT_F_RTS)); + sp->dstnode = cb->dst; + sp->srcnode = cb->src; + sp->forward = cb->hops & 0x3f; + + skb_reset_network_header(skb); + + return NF_HOOK(NFPROTO_DECNET, NF_DN_POST_ROUTING, sk, skb, + NULL, neigh->dev, dn_neigh_output_packet); +} + +/* + * For talking to DECnet phase III nodes + * Phase 3 output is the same as short output, execpt that + * it clears the area bits before transmission. + */ +static int dn_phase3_output(struct neighbour *neigh, struct sock *sk, + struct sk_buff *skb) +{ + struct net_device *dev = neigh->dev; + int headroom = dev->hard_header_len + sizeof(struct dn_short_packet) + 2; + struct dn_short_packet *sp; + unsigned char *data; + struct dn_skb_cb *cb = DN_SKB_CB(skb); + + if (skb_headroom(skb) < headroom) { + struct sk_buff *skb2 = skb_realloc_headroom(skb, headroom); + if (skb2 == NULL) { + net_crit_ratelimited("dn_phase3_output: no memory\n"); + kfree_skb(skb); + return -ENOBUFS; + } + consume_skb(skb); + skb = skb2; + net_info_ratelimited("dn_phase3_output: Increasing headroom\n"); + } + + data = skb_push(skb, sizeof(struct dn_short_packet) + 2); + *((__le16 *)data) = cpu_to_le16(skb->len - 2); + sp = (struct dn_short_packet *)(data + 2); + + sp->msgflg = DN_RT_PKT_SHORT|(cb->rt_flags&(DN_RT_F_RQR|DN_RT_F_RTS)); + sp->dstnode = cb->dst & cpu_to_le16(0x03ff); + sp->srcnode = cb->src & cpu_to_le16(0x03ff); + sp->forward = cb->hops & 0x3f; + + skb_reset_network_header(skb); + + return NF_HOOK(NFPROTO_DECNET, NF_DN_POST_ROUTING, sk, skb, + NULL, neigh->dev, dn_neigh_output_packet); +} + +int dn_to_neigh_output(struct sock *sk, struct sk_buff *skb) +{ + struct dst_entry *dst = skb_dst(skb); + struct dn_route *rt = (struct dn_route *) dst; + struct neighbour *neigh = rt->n; + struct dn_neigh *dn = (struct dn_neigh *)neigh; + struct dn_dev *dn_db; + bool use_long; + + rcu_read_lock(); + dn_db = rcu_dereference(neigh->dev->dn_ptr); + if (dn_db == NULL) { + rcu_read_unlock(); + return -EINVAL; + } + use_long = dn_db->use_long; + rcu_read_unlock(); + + if (dn->flags & DN_NDFLAG_P3) + return dn_phase3_output(neigh, sk, skb); + if (use_long) + return dn_long_output(neigh, sk, skb); + else + return dn_short_output(neigh, sk, skb); +} + +/* + * Unfortunately, the neighbour code uses the device in its hash + * function, so we don't get any advantage from it. This function + * basically does a neigh_lookup(), but without comparing the device + * field. This is required for the On-Ethernet cache + */ + +/* + * Pointopoint link receives a hello message + */ +void dn_neigh_pointopoint_hello(struct sk_buff *skb) +{ + kfree_skb(skb); +} + +/* + * Ethernet router hello message received + */ +int dn_neigh_router_hello(struct sock *sk, struct sk_buff *skb) +{ + struct rtnode_hello_message *msg = (struct rtnode_hello_message *)skb->data; + + struct neighbour *neigh; + struct dn_neigh *dn; + struct dn_dev *dn_db; + __le16 src; + + src = dn_eth2dn(msg->id); + + neigh = __neigh_lookup(&dn_neigh_table, &src, skb->dev, 1); + + dn = (struct dn_neigh *)neigh; + + if (neigh) { + write_lock(&neigh->lock); + + neigh->used = jiffies; + dn_db = rcu_dereference(neigh->dev->dn_ptr); + + if (!(neigh->nud_state & NUD_PERMANENT)) { + neigh->updated = jiffies; + + if (neigh->dev->type == ARPHRD_ETHER) + memcpy(neigh->ha, ð_hdr(skb)->h_source, ETH_ALEN); + + dn->blksize = le16_to_cpu(msg->blksize); + dn->priority = msg->priority; + + dn->flags &= ~DN_NDFLAG_P3; + + switch (msg->iinfo & DN_RT_INFO_TYPE) { + case DN_RT_INFO_L1RT: + dn->flags &=~DN_NDFLAG_R2; + dn->flags |= DN_NDFLAG_R1; + break; + case DN_RT_INFO_L2RT: + dn->flags |= DN_NDFLAG_R2; + } + } + + /* Only use routers in our area */ + if ((le16_to_cpu(src)>>10) == (le16_to_cpu((decnet_address))>>10)) { + if (!dn_db->router) { + dn_db->router = neigh_clone(neigh); + } else { + if (msg->priority > ((struct dn_neigh *)dn_db->router)->priority) + neigh_release(xchg(&dn_db->router, neigh_clone(neigh))); + } + } + write_unlock(&neigh->lock); + neigh_release(neigh); + } + + kfree_skb(skb); + return 0; +} + +/* + * Endnode hello message received + */ +int dn_neigh_endnode_hello(struct sock *sk, struct sk_buff *skb) +{ + struct endnode_hello_message *msg = (struct endnode_hello_message *)skb->data; + struct neighbour *neigh; + struct dn_neigh *dn; + __le16 src; + + src = dn_eth2dn(msg->id); + + neigh = __neigh_lookup(&dn_neigh_table, &src, skb->dev, 1); + + dn = (struct dn_neigh *)neigh; + + if (neigh) { + write_lock(&neigh->lock); + + neigh->used = jiffies; + + if (!(neigh->nud_state & NUD_PERMANENT)) { + neigh->updated = jiffies; + + if (neigh->dev->type == ARPHRD_ETHER) + memcpy(neigh->ha, ð_hdr(skb)->h_source, ETH_ALEN); + dn->flags &= ~(DN_NDFLAG_R1 | DN_NDFLAG_R2); + dn->blksize = le16_to_cpu(msg->blksize); + dn->priority = 0; + } + + write_unlock(&neigh->lock); + neigh_release(neigh); + } + + kfree_skb(skb); + return 0; +} + +static char *dn_find_slot(char *base, int max, int priority) +{ + int i; + unsigned char *min = NULL; + + base += 6; /* skip first id */ + + for(i = 0; i < max; i++) { + if (!min || (*base < *min)) + min = base; + base += 7; /* find next priority */ + } + + if (!min) + return NULL; + + return (*min < priority) ? (min - 6) : NULL; +} + +struct elist_cb_state { + struct net_device *dev; + unsigned char *ptr; + unsigned char *rs; + int t, n; +}; + +static void neigh_elist_cb(struct neighbour *neigh, void *_info) +{ + struct elist_cb_state *s = _info; + struct dn_neigh *dn; + + if (neigh->dev != s->dev) + return; + + dn = (struct dn_neigh *) neigh; + if (!(dn->flags & (DN_NDFLAG_R1|DN_NDFLAG_R2))) + return; + + if (s->t == s->n) + s->rs = dn_find_slot(s->ptr, s->n, dn->priority); + else + s->t++; + if (s->rs == NULL) + return; + + dn_dn2eth(s->rs, dn->addr); + s->rs += 6; + *(s->rs) = neigh->nud_state & NUD_CONNECTED ? 0x80 : 0x0; + *(s->rs) |= dn->priority; + s->rs++; +} + +int dn_neigh_elist(struct net_device *dev, unsigned char *ptr, int n) +{ + struct elist_cb_state state; + + state.dev = dev; + state.t = 0; + state.n = n; + state.ptr = ptr; + state.rs = ptr; + + neigh_for_each(&dn_neigh_table, neigh_elist_cb, &state); + + return state.t; +} + + +#ifdef CONFIG_PROC_FS + +static inline void dn_neigh_format_entry(struct seq_file *seq, + struct neighbour *n) +{ + struct dn_neigh *dn = (struct dn_neigh *) n; + char buf[DN_ASCBUF_LEN]; + + read_lock(&n->lock); + seq_printf(seq, "%-7s %s%s%s %02x %02d %07ld %-8s\n", + dn_addr2asc(le16_to_cpu(dn->addr), buf), + (dn->flags&DN_NDFLAG_R1) ? "1" : "-", + (dn->flags&DN_NDFLAG_R2) ? "2" : "-", + (dn->flags&DN_NDFLAG_P3) ? "3" : "-", + dn->n.nud_state, + atomic_read(&dn->n.refcnt), + dn->blksize, + (dn->n.dev) ? dn->n.dev->name : "?"); + read_unlock(&n->lock); +} + +static int dn_neigh_seq_show(struct seq_file *seq, void *v) +{ + if (v == SEQ_START_TOKEN) { + seq_puts(seq, "Addr Flags State Use Blksize Dev\n"); + } else { + dn_neigh_format_entry(seq, v); + } + + return 0; +} + +static void *dn_neigh_seq_start(struct seq_file *seq, loff_t *pos) +{ + return neigh_seq_start(seq, pos, &dn_neigh_table, + NEIGH_SEQ_NEIGH_ONLY); +} + +static const struct seq_operations dn_neigh_seq_ops = { + .start = dn_neigh_seq_start, + .next = neigh_seq_next, + .stop = neigh_seq_stop, + .show = dn_neigh_seq_show, +}; + +static int dn_neigh_seq_open(struct inode *inode, struct file *file) +{ + return seq_open_net(inode, file, &dn_neigh_seq_ops, + sizeof(struct neigh_seq_state)); +} + +static const struct file_operations dn_neigh_seq_fops = { + .owner = THIS_MODULE, + .open = dn_neigh_seq_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release_net, +}; + +#endif + +void __init dn_neigh_init(void) +{ + neigh_table_init(NEIGH_DN_TABLE, &dn_neigh_table); + proc_create("decnet_neigh", S_IRUGO, init_net.proc_net, + &dn_neigh_seq_fops); +} + +void __exit dn_neigh_cleanup(void) +{ + remove_proc_entry("decnet_neigh", init_net.proc_net); + neigh_table_clear(NEIGH_DN_TABLE, &dn_neigh_table); +} diff --git a/net/decnet/dn_nsp_in.c b/net/decnet/dn_nsp_in.c new file mode 100644 index 000000000..a321eac9f --- /dev/null +++ b/net/decnet/dn_nsp_in.c @@ -0,0 +1,917 @@ +/* + * DECnet An implementation of the DECnet protocol suite for the LINUX + * operating system. DECnet is implemented using the BSD Socket + * interface as the means of communication with the user level. + * + * DECnet Network Services Protocol (Input) + * + * Author: Eduardo Marcelo Serrat <emserrat@geocities.com> + * + * Changes: + * + * Steve Whitehouse: Split into dn_nsp_in.c and dn_nsp_out.c from + * original dn_nsp.c. + * Steve Whitehouse: Updated to work with my new routing architecture. + * Steve Whitehouse: Add changes from Eduardo Serrat's patches. + * Steve Whitehouse: Put all ack handling code in a common routine. + * Steve Whitehouse: Put other common bits into dn_nsp_rx() + * Steve Whitehouse: More checks on skb->len to catch bogus packets + * Fixed various race conditions and possible nasties. + * Steve Whitehouse: Now handles returned conninit frames. + * David S. Miller: New socket locking + * Steve Whitehouse: Fixed lockup when socket filtering was enabled. + * Paul Koning: Fix to push CC sockets into RUN when acks are + * received. + * Steve Whitehouse: + * Patrick Caulfield: Checking conninits for correctness & sending of error + * responses. + * Steve Whitehouse: Added backlog congestion level return codes. + * Patrick Caulfield: + * Steve Whitehouse: Added flow control support (outbound) + * Steve Whitehouse: Prepare for nonlinear skbs + */ + +/****************************************************************************** + (c) 1995-1998 E.M. Serrat emserrat@geocities.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. +*******************************************************************************/ + +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/socket.h> +#include <linux/in.h> +#include <linux/kernel.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/slab.h> +#include <net/sock.h> +#include <net/tcp_states.h> +#include <linux/fcntl.h> +#include <linux/mm.h> +#include <linux/termios.h> +#include <linux/interrupt.h> +#include <linux/proc_fs.h> +#include <linux/stat.h> +#include <linux/init.h> +#include <linux/poll.h> +#include <linux/netfilter_decnet.h> +#include <net/neighbour.h> +#include <net/dst.h> +#include <net/dn.h> +#include <net/dn_nsp.h> +#include <net/dn_dev.h> +#include <net/dn_route.h> + +extern int decnet_log_martians; + +static void dn_log_martian(struct sk_buff *skb, const char *msg) +{ + if (decnet_log_martians) { + char *devname = skb->dev ? skb->dev->name : "???"; + struct dn_skb_cb *cb = DN_SKB_CB(skb); + net_info_ratelimited("DECnet: Martian packet (%s) dev=%s src=0x%04hx dst=0x%04hx srcport=0x%04hx dstport=0x%04hx\n", + msg, devname, + le16_to_cpu(cb->src), + le16_to_cpu(cb->dst), + le16_to_cpu(cb->src_port), + le16_to_cpu(cb->dst_port)); + } +} + +/* + * For this function we've flipped the cross-subchannel bit + * if the message is an otherdata or linkservice message. Thus + * we can use it to work out what to update. + */ +static void dn_ack(struct sock *sk, struct sk_buff *skb, unsigned short ack) +{ + struct dn_scp *scp = DN_SK(sk); + unsigned short type = ((ack >> 12) & 0x0003); + int wakeup = 0; + + switch (type) { + case 0: /* ACK - Data */ + if (dn_after(ack, scp->ackrcv_dat)) { + scp->ackrcv_dat = ack & 0x0fff; + wakeup |= dn_nsp_check_xmit_queue(sk, skb, + &scp->data_xmit_queue, + ack); + } + break; + case 1: /* NAK - Data */ + break; + case 2: /* ACK - OtherData */ + if (dn_after(ack, scp->ackrcv_oth)) { + scp->ackrcv_oth = ack & 0x0fff; + wakeup |= dn_nsp_check_xmit_queue(sk, skb, + &scp->other_xmit_queue, + ack); + } + break; + case 3: /* NAK - OtherData */ + break; + } + + if (wakeup && !sock_flag(sk, SOCK_DEAD)) + sk->sk_state_change(sk); +} + +/* + * This function is a universal ack processor. + */ +static int dn_process_ack(struct sock *sk, struct sk_buff *skb, int oth) +{ + __le16 *ptr = (__le16 *)skb->data; + int len = 0; + unsigned short ack; + + if (skb->len < 2) + return len; + + if ((ack = le16_to_cpu(*ptr)) & 0x8000) { + skb_pull(skb, 2); + ptr++; + len += 2; + if ((ack & 0x4000) == 0) { + if (oth) + ack ^= 0x2000; + dn_ack(sk, skb, ack); + } + } + + if (skb->len < 2) + return len; + + if ((ack = le16_to_cpu(*ptr)) & 0x8000) { + skb_pull(skb, 2); + len += 2; + if ((ack & 0x4000) == 0) { + if (oth) + ack ^= 0x2000; + dn_ack(sk, skb, ack); + } + } + + return len; +} + + +/** + * dn_check_idf - Check an image data field format is correct. + * @pptr: Pointer to pointer to image data + * @len: Pointer to length of image data + * @max: The maximum allowed length of the data in the image data field + * @follow_on: Check that this many bytes exist beyond the end of the image data + * + * Returns: 0 if ok, -1 on error + */ +static inline int dn_check_idf(unsigned char **pptr, int *len, unsigned char max, unsigned char follow_on) +{ + unsigned char *ptr = *pptr; + unsigned char flen = *ptr++; + + (*len)--; + if (flen > max) + return -1; + if ((flen + follow_on) > *len) + return -1; + + *len -= flen; + *pptr = ptr + flen; + return 0; +} + +/* + * Table of reason codes to pass back to node which sent us a badly + * formed message, plus text messages for the log. A zero entry in + * the reason field means "don't reply" otherwise a disc init is sent with + * the specified reason code. + */ +static struct { + unsigned short reason; + const char *text; +} ci_err_table[] = { + { 0, "CI: Truncated message" }, + { NSP_REASON_ID, "CI: Destination username error" }, + { NSP_REASON_ID, "CI: Destination username type" }, + { NSP_REASON_US, "CI: Source username error" }, + { 0, "CI: Truncated at menuver" }, + { 0, "CI: Truncated before access or user data" }, + { NSP_REASON_IO, "CI: Access data format error" }, + { NSP_REASON_IO, "CI: User data format error" } +}; + +/* + * This function uses a slightly different lookup method + * to find its sockets, since it searches on object name/number + * rather than port numbers. Various tests are done to ensure that + * the incoming data is in the correct format before it is queued to + * a socket. + */ +static struct sock *dn_find_listener(struct sk_buff *skb, unsigned short *reason) +{ + struct dn_skb_cb *cb = DN_SKB_CB(skb); + struct nsp_conn_init_msg *msg = (struct nsp_conn_init_msg *)skb->data; + struct sockaddr_dn dstaddr; + struct sockaddr_dn srcaddr; + unsigned char type = 0; + int dstlen; + int srclen; + unsigned char *ptr; + int len; + int err = 0; + unsigned char menuver; + + memset(&dstaddr, 0, sizeof(struct sockaddr_dn)); + memset(&srcaddr, 0, sizeof(struct sockaddr_dn)); + + /* + * 1. Decode & remove message header + */ + cb->src_port = msg->srcaddr; + cb->dst_port = msg->dstaddr; + cb->services = msg->services; + cb->info = msg->info; + cb->segsize = le16_to_cpu(msg->segsize); + + if (!pskb_may_pull(skb, sizeof(*msg))) + goto err_out; + + skb_pull(skb, sizeof(*msg)); + + len = skb->len; + ptr = skb->data; + + /* + * 2. Check destination end username format + */ + dstlen = dn_username2sockaddr(ptr, len, &dstaddr, &type); + err++; + if (dstlen < 0) + goto err_out; + + err++; + if (type > 1) + goto err_out; + + len -= dstlen; + ptr += dstlen; + + /* + * 3. Check source end username format + */ + srclen = dn_username2sockaddr(ptr, len, &srcaddr, &type); + err++; + if (srclen < 0) + goto err_out; + + len -= srclen; + ptr += srclen; + err++; + if (len < 1) + goto err_out; + + menuver = *ptr; + ptr++; + len--; + + /* + * 4. Check that optional data actually exists if menuver says it does + */ + err++; + if ((menuver & (DN_MENUVER_ACC | DN_MENUVER_USR)) && (len < 1)) + goto err_out; + + /* + * 5. Check optional access data format + */ + err++; + if (menuver & DN_MENUVER_ACC) { + if (dn_check_idf(&ptr, &len, 39, 1)) + goto err_out; + if (dn_check_idf(&ptr, &len, 39, 1)) + goto err_out; + if (dn_check_idf(&ptr, &len, 39, (menuver & DN_MENUVER_USR) ? 1 : 0)) + goto err_out; + } + + /* + * 6. Check optional user data format + */ + err++; + if (menuver & DN_MENUVER_USR) { + if (dn_check_idf(&ptr, &len, 16, 0)) + goto err_out; + } + + /* + * 7. Look up socket based on destination end username + */ + return dn_sklist_find_listener(&dstaddr); +err_out: + dn_log_martian(skb, ci_err_table[err].text); + *reason = ci_err_table[err].reason; + return NULL; +} + + +static void dn_nsp_conn_init(struct sock *sk, struct sk_buff *skb) +{ + if (sk_acceptq_is_full(sk)) { + kfree_skb(skb); + return; + } + + sk->sk_ack_backlog++; + skb_queue_tail(&sk->sk_receive_queue, skb); + sk->sk_state_change(sk); +} + +static void dn_nsp_conn_conf(struct sock *sk, struct sk_buff *skb) +{ + struct dn_skb_cb *cb = DN_SKB_CB(skb); + struct dn_scp *scp = DN_SK(sk); + unsigned char *ptr; + + if (skb->len < 4) + goto out; + + ptr = skb->data; + cb->services = *ptr++; + cb->info = *ptr++; + cb->segsize = le16_to_cpu(*(__le16 *)ptr); + + if ((scp->state == DN_CI) || (scp->state == DN_CD)) { + scp->persist = 0; + scp->addrrem = cb->src_port; + sk->sk_state = TCP_ESTABLISHED; + scp->state = DN_RUN; + scp->services_rem = cb->services; + scp->info_rem = cb->info; + scp->segsize_rem = cb->segsize; + + if ((scp->services_rem & NSP_FC_MASK) == NSP_FC_NONE) + scp->max_window = decnet_no_fc_max_cwnd; + + if (skb->len > 0) { + u16 dlen = *skb->data; + if ((dlen <= 16) && (dlen <= skb->len)) { + scp->conndata_in.opt_optl = cpu_to_le16(dlen); + skb_copy_from_linear_data_offset(skb, 1, + scp->conndata_in.opt_data, dlen); + } + } + dn_nsp_send_link(sk, DN_NOCHANGE, 0); + if (!sock_flag(sk, SOCK_DEAD)) + sk->sk_state_change(sk); + } + +out: + kfree_skb(skb); +} + +static void dn_nsp_conn_ack(struct sock *sk, struct sk_buff *skb) +{ + struct dn_scp *scp = DN_SK(sk); + + if (scp->state == DN_CI) { + scp->state = DN_CD; + scp->persist = 0; + } + + kfree_skb(skb); +} + +static void dn_nsp_disc_init(struct sock *sk, struct sk_buff *skb) +{ + struct dn_scp *scp = DN_SK(sk); + struct dn_skb_cb *cb = DN_SKB_CB(skb); + unsigned short reason; + + if (skb->len < 2) + goto out; + + reason = le16_to_cpu(*(__le16 *)skb->data); + skb_pull(skb, 2); + + scp->discdata_in.opt_status = cpu_to_le16(reason); + scp->discdata_in.opt_optl = 0; + memset(scp->discdata_in.opt_data, 0, 16); + + if (skb->len > 0) { + u16 dlen = *skb->data; + if ((dlen <= 16) && (dlen <= skb->len)) { + scp->discdata_in.opt_optl = cpu_to_le16(dlen); + skb_copy_from_linear_data_offset(skb, 1, scp->discdata_in.opt_data, dlen); + } + } + + scp->addrrem = cb->src_port; + sk->sk_state = TCP_CLOSE; + + switch (scp->state) { + case DN_CI: + case DN_CD: + scp->state = DN_RJ; + sk->sk_err = ECONNREFUSED; + break; + case DN_RUN: + sk->sk_shutdown |= SHUTDOWN_MASK; + scp->state = DN_DN; + break; + case DN_DI: + scp->state = DN_DIC; + break; + } + + if (!sock_flag(sk, SOCK_DEAD)) { + if (sk->sk_socket->state != SS_UNCONNECTED) + sk->sk_socket->state = SS_DISCONNECTING; + sk->sk_state_change(sk); + } + + /* + * It appears that its possible for remote machines to send disc + * init messages with no port identifier if we are in the CI and + * possibly also the CD state. Obviously we shouldn't reply with + * a message if we don't know what the end point is. + */ + if (scp->addrrem) { + dn_nsp_send_disc(sk, NSP_DISCCONF, NSP_REASON_DC, GFP_ATOMIC); + } + scp->persist_fxn = dn_destroy_timer; + scp->persist = dn_nsp_persist(sk); + +out: + kfree_skb(skb); +} + +/* + * disc_conf messages are also called no_resources or no_link + * messages depending upon the "reason" field. + */ +static void dn_nsp_disc_conf(struct sock *sk, struct sk_buff *skb) +{ + struct dn_scp *scp = DN_SK(sk); + unsigned short reason; + + if (skb->len != 2) + goto out; + + reason = le16_to_cpu(*(__le16 *)skb->data); + + sk->sk_state = TCP_CLOSE; + + switch (scp->state) { + case DN_CI: + scp->state = DN_NR; + break; + case DN_DR: + if (reason == NSP_REASON_DC) + scp->state = DN_DRC; + if (reason == NSP_REASON_NL) + scp->state = DN_CN; + break; + case DN_DI: + scp->state = DN_DIC; + break; + case DN_RUN: + sk->sk_shutdown |= SHUTDOWN_MASK; + case DN_CC: + scp->state = DN_CN; + } + + if (!sock_flag(sk, SOCK_DEAD)) { + if (sk->sk_socket->state != SS_UNCONNECTED) + sk->sk_socket->state = SS_DISCONNECTING; + sk->sk_state_change(sk); + } + + scp->persist_fxn = dn_destroy_timer; + scp->persist = dn_nsp_persist(sk); + +out: + kfree_skb(skb); +} + +static void dn_nsp_linkservice(struct sock *sk, struct sk_buff *skb) +{ + struct dn_scp *scp = DN_SK(sk); + unsigned short segnum; + unsigned char lsflags; + signed char fcval; + int wake_up = 0; + char *ptr = skb->data; + unsigned char fctype = scp->services_rem & NSP_FC_MASK; + + if (skb->len != 4) + goto out; + + segnum = le16_to_cpu(*(__le16 *)ptr); + ptr += 2; + lsflags = *(unsigned char *)ptr++; + fcval = *ptr; + + /* + * Here we ignore erronous packets which should really + * should cause a connection abort. It is not critical + * for now though. + */ + if (lsflags & 0xf8) + goto out; + + if (seq_next(scp->numoth_rcv, segnum)) { + seq_add(&scp->numoth_rcv, 1); + switch(lsflags & 0x04) { /* FCVAL INT */ + case 0x00: /* Normal Request */ + switch(lsflags & 0x03) { /* FCVAL MOD */ + case 0x00: /* Request count */ + if (fcval < 0) { + unsigned char p_fcval = -fcval; + if ((scp->flowrem_dat > p_fcval) && + (fctype == NSP_FC_SCMC)) { + scp->flowrem_dat -= p_fcval; + } + } else if (fcval > 0) { + scp->flowrem_dat += fcval; + wake_up = 1; + } + break; + case 0x01: /* Stop outgoing data */ + scp->flowrem_sw = DN_DONTSEND; + break; + case 0x02: /* Ok to start again */ + scp->flowrem_sw = DN_SEND; + dn_nsp_output(sk); + wake_up = 1; + } + break; + case 0x04: /* Interrupt Request */ + if (fcval > 0) { + scp->flowrem_oth += fcval; + wake_up = 1; + } + break; + } + if (wake_up && !sock_flag(sk, SOCK_DEAD)) + sk->sk_state_change(sk); + } + + dn_nsp_send_oth_ack(sk); + +out: + kfree_skb(skb); +} + +/* + * Copy of sock_queue_rcv_skb (from sock.h) without + * bh_lock_sock() (its already held when this is called) which + * also allows data and other data to be queued to a socket. + */ +static __inline__ int dn_queue_skb(struct sock *sk, struct sk_buff *skb, int sig, struct sk_buff_head *queue) +{ + int err; + + /* Cast skb->rcvbuf to unsigned... It's pointless, but reduces + number of warnings when compiling with -W --ANK + */ + if (atomic_read(&sk->sk_rmem_alloc) + skb->truesize >= + (unsigned int)sk->sk_rcvbuf) { + err = -ENOMEM; + goto out; + } + + err = sk_filter(sk, skb); + if (err) + goto out; + + skb_set_owner_r(skb, sk); + skb_queue_tail(queue, skb); + + if (!sock_flag(sk, SOCK_DEAD)) + sk->sk_data_ready(sk); +out: + return err; +} + +static void dn_nsp_otherdata(struct sock *sk, struct sk_buff *skb) +{ + struct dn_scp *scp = DN_SK(sk); + unsigned short segnum; + struct dn_skb_cb *cb = DN_SKB_CB(skb); + int queued = 0; + + if (skb->len < 2) + goto out; + + cb->segnum = segnum = le16_to_cpu(*(__le16 *)skb->data); + skb_pull(skb, 2); + + if (seq_next(scp->numoth_rcv, segnum)) { + + if (dn_queue_skb(sk, skb, SIGURG, &scp->other_receive_queue) == 0) { + seq_add(&scp->numoth_rcv, 1); + scp->other_report = 0; + queued = 1; + } + } + + dn_nsp_send_oth_ack(sk); +out: + if (!queued) + kfree_skb(skb); +} + +static void dn_nsp_data(struct sock *sk, struct sk_buff *skb) +{ + int queued = 0; + unsigned short segnum; + struct dn_skb_cb *cb = DN_SKB_CB(skb); + struct dn_scp *scp = DN_SK(sk); + + if (skb->len < 2) + goto out; + + cb->segnum = segnum = le16_to_cpu(*(__le16 *)skb->data); + skb_pull(skb, 2); + + if (seq_next(scp->numdat_rcv, segnum)) { + if (dn_queue_skb(sk, skb, SIGIO, &sk->sk_receive_queue) == 0) { + seq_add(&scp->numdat_rcv, 1); + queued = 1; + } + + if ((scp->flowloc_sw == DN_SEND) && dn_congested(sk)) { + scp->flowloc_sw = DN_DONTSEND; + dn_nsp_send_link(sk, DN_DONTSEND, 0); + } + } + + dn_nsp_send_data_ack(sk); +out: + if (!queued) + kfree_skb(skb); +} + +/* + * If one of our conninit messages is returned, this function + * deals with it. It puts the socket into the NO_COMMUNICATION + * state. + */ +static void dn_returned_conn_init(struct sock *sk, struct sk_buff *skb) +{ + struct dn_scp *scp = DN_SK(sk); + + if (scp->state == DN_CI) { + scp->state = DN_NC; + sk->sk_state = TCP_CLOSE; + if (!sock_flag(sk, SOCK_DEAD)) + sk->sk_state_change(sk); + } + + kfree_skb(skb); +} + +static int dn_nsp_no_socket(struct sk_buff *skb, unsigned short reason) +{ + struct dn_skb_cb *cb = DN_SKB_CB(skb); + int ret = NET_RX_DROP; + + /* Must not reply to returned packets */ + if (cb->rt_flags & DN_RT_F_RTS) + goto out; + + if ((reason != NSP_REASON_OK) && ((cb->nsp_flags & 0x0c) == 0x08)) { + switch (cb->nsp_flags & 0x70) { + case 0x10: + case 0x60: /* (Retransmitted) Connect Init */ + dn_nsp_return_disc(skb, NSP_DISCINIT, reason); + ret = NET_RX_SUCCESS; + break; + case 0x20: /* Connect Confirm */ + dn_nsp_return_disc(skb, NSP_DISCCONF, reason); + ret = NET_RX_SUCCESS; + break; + } + } + +out: + kfree_skb(skb); + return ret; +} + +static int dn_nsp_rx_packet(struct sock *sk2, struct sk_buff *skb) +{ + struct dn_skb_cb *cb = DN_SKB_CB(skb); + struct sock *sk = NULL; + unsigned char *ptr = (unsigned char *)skb->data; + unsigned short reason = NSP_REASON_NL; + + if (!pskb_may_pull(skb, 2)) + goto free_out; + + skb_reset_transport_header(skb); + cb->nsp_flags = *ptr++; + + if (decnet_debug_level & 2) + printk(KERN_DEBUG "dn_nsp_rx: Message type 0x%02x\n", (int)cb->nsp_flags); + + if (cb->nsp_flags & 0x83) + goto free_out; + + /* + * Filter out conninits and useless packet types + */ + if ((cb->nsp_flags & 0x0c) == 0x08) { + switch (cb->nsp_flags & 0x70) { + case 0x00: /* NOP */ + case 0x70: /* Reserved */ + case 0x50: /* Reserved, Phase II node init */ + goto free_out; + case 0x10: + case 0x60: + if (unlikely(cb->rt_flags & DN_RT_F_RTS)) + goto free_out; + sk = dn_find_listener(skb, &reason); + goto got_it; + } + } + + if (!pskb_may_pull(skb, 3)) + goto free_out; + + /* + * Grab the destination address. + */ + cb->dst_port = *(__le16 *)ptr; + cb->src_port = 0; + ptr += 2; + + /* + * If not a connack, grab the source address too. + */ + if (pskb_may_pull(skb, 5)) { + cb->src_port = *(__le16 *)ptr; + ptr += 2; + skb_pull(skb, 5); + } + + /* + * Returned packets... + * Swap src & dst and look up in the normal way. + */ + if (unlikely(cb->rt_flags & DN_RT_F_RTS)) { + __le16 tmp = cb->dst_port; + cb->dst_port = cb->src_port; + cb->src_port = tmp; + tmp = cb->dst; + cb->dst = cb->src; + cb->src = tmp; + } + + /* + * Find the socket to which this skb is destined. + */ + sk = dn_find_by_skb(skb); +got_it: + if (sk != NULL) { + struct dn_scp *scp = DN_SK(sk); + + /* Reset backoff */ + scp->nsp_rxtshift = 0; + + /* + * We linearize everything except data segments here. + */ + if (cb->nsp_flags & ~0x60) { + if (unlikely(skb_linearize(skb))) + goto free_out; + } + + return sk_receive_skb(sk, skb, 0); + } + + return dn_nsp_no_socket(skb, reason); + +free_out: + kfree_skb(skb); + return NET_RX_DROP; +} + +int dn_nsp_rx(struct sk_buff *skb) +{ + return NF_HOOK(NFPROTO_DECNET, NF_DN_LOCAL_IN, NULL, skb, + skb->dev, NULL, + dn_nsp_rx_packet); +} + +/* + * This is the main receive routine for sockets. It is called + * from the above when the socket is not busy, and also from + * sock_release() when there is a backlog queued up. + */ +int dn_nsp_backlog_rcv(struct sock *sk, struct sk_buff *skb) +{ + struct dn_scp *scp = DN_SK(sk); + struct dn_skb_cb *cb = DN_SKB_CB(skb); + + if (cb->rt_flags & DN_RT_F_RTS) { + if (cb->nsp_flags == 0x18 || cb->nsp_flags == 0x68) + dn_returned_conn_init(sk, skb); + else + kfree_skb(skb); + return NET_RX_SUCCESS; + } + + /* + * Control packet. + */ + if ((cb->nsp_flags & 0x0c) == 0x08) { + switch (cb->nsp_flags & 0x70) { + case 0x10: + case 0x60: + dn_nsp_conn_init(sk, skb); + break; + case 0x20: + dn_nsp_conn_conf(sk, skb); + break; + case 0x30: + dn_nsp_disc_init(sk, skb); + break; + case 0x40: + dn_nsp_disc_conf(sk, skb); + break; + } + + } else if (cb->nsp_flags == 0x24) { + /* + * Special for connacks, 'cos they don't have + * ack data or ack otherdata info. + */ + dn_nsp_conn_ack(sk, skb); + } else { + int other = 1; + + /* both data and ack frames can kick a CC socket into RUN */ + if ((scp->state == DN_CC) && !sock_flag(sk, SOCK_DEAD)) { + scp->state = DN_RUN; + sk->sk_state = TCP_ESTABLISHED; + sk->sk_state_change(sk); + } + + if ((cb->nsp_flags & 0x1c) == 0) + other = 0; + if (cb->nsp_flags == 0x04) + other = 0; + + /* + * Read out ack data here, this applies equally + * to data, other data, link serivce and both + * ack data and ack otherdata. + */ + dn_process_ack(sk, skb, other); + + /* + * If we've some sort of data here then call a + * suitable routine for dealing with it, otherwise + * the packet is an ack and can be discarded. + */ + if ((cb->nsp_flags & 0x0c) == 0) { + + if (scp->state != DN_RUN) + goto free_out; + + switch (cb->nsp_flags) { + case 0x10: /* LS */ + dn_nsp_linkservice(sk, skb); + break; + case 0x30: /* OD */ + dn_nsp_otherdata(sk, skb); + break; + default: + dn_nsp_data(sk, skb); + } + + } else { /* Ack, chuck it out here */ +free_out: + kfree_skb(skb); + } + } + + return NET_RX_SUCCESS; +} + diff --git a/net/decnet/dn_nsp_out.c b/net/decnet/dn_nsp_out.c new file mode 100644 index 000000000..1aaa51ebb --- /dev/null +++ b/net/decnet/dn_nsp_out.c @@ -0,0 +1,718 @@ +/* + * DECnet An implementation of the DECnet protocol suite for the LINUX + * operating system. DECnet is implemented using the BSD Socket + * interface as the means of communication with the user level. + * + * DECnet Network Services Protocol (Output) + * + * Author: Eduardo Marcelo Serrat <emserrat@geocities.com> + * + * Changes: + * + * Steve Whitehouse: Split into dn_nsp_in.c and dn_nsp_out.c from + * original dn_nsp.c. + * Steve Whitehouse: Updated to work with my new routing architecture. + * Steve Whitehouse: Added changes from Eduardo Serrat's patches. + * Steve Whitehouse: Now conninits have the "return" bit set. + * Steve Whitehouse: Fixes to check alloc'd skbs are non NULL! + * Moved output state machine into one function + * Steve Whitehouse: New output state machine + * Paul Koning: Connect Confirm message fix. + * Eduardo Serrat: Fix to stop dn_nsp_do_disc() sending malformed packets. + * Steve Whitehouse: dn_nsp_output() and friends needed a spring clean + * Steve Whitehouse: Moved dn_nsp_send() in here from route.h + */ + +/****************************************************************************** + (c) 1995-1998 E.M. Serrat emserrat@geocities.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. +*******************************************************************************/ + +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/socket.h> +#include <linux/in.h> +#include <linux/kernel.h> +#include <linux/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/slab.h> +#include <net/sock.h> +#include <linux/fcntl.h> +#include <linux/mm.h> +#include <linux/termios.h> +#include <linux/interrupt.h> +#include <linux/proc_fs.h> +#include <linux/stat.h> +#include <linux/init.h> +#include <linux/poll.h> +#include <linux/if_packet.h> +#include <net/neighbour.h> +#include <net/dst.h> +#include <net/flow.h> +#include <net/dn.h> +#include <net/dn_nsp.h> +#include <net/dn_dev.h> +#include <net/dn_route.h> + + +static int nsp_backoff[NSP_MAXRXTSHIFT + 1] = { 1, 2, 4, 8, 16, 32, 64, 64, 64, 64, 64, 64, 64 }; + +static void dn_nsp_send(struct sk_buff *skb) +{ + struct sock *sk = skb->sk; + struct dn_scp *scp = DN_SK(sk); + struct dst_entry *dst; + struct flowidn fld; + + skb_reset_transport_header(skb); + scp->stamp = jiffies; + + dst = sk_dst_check(sk, 0); + if (dst) { +try_again: + skb_dst_set(skb, dst); + dst_output(skb); + return; + } + + memset(&fld, 0, sizeof(fld)); + fld.flowidn_oif = sk->sk_bound_dev_if; + fld.saddr = dn_saddr2dn(&scp->addr); + fld.daddr = dn_saddr2dn(&scp->peer); + dn_sk_ports_copy(&fld, scp); + fld.flowidn_proto = DNPROTO_NSP; + if (dn_route_output_sock(&sk->sk_dst_cache, &fld, sk, 0) == 0) { + dst = sk_dst_get(sk); + sk->sk_route_caps = dst->dev->features; + goto try_again; + } + + sk->sk_err = EHOSTUNREACH; + if (!sock_flag(sk, SOCK_DEAD)) + sk->sk_state_change(sk); +} + + +/* + * If sk == NULL, then we assume that we are supposed to be making + * a routing layer skb. If sk != NULL, then we are supposed to be + * creating an skb for the NSP layer. + * + * The eventual aim is for each socket to have a cached header size + * for its outgoing packets, and to set hdr from this when sk != NULL. + */ +struct sk_buff *dn_alloc_skb(struct sock *sk, int size, gfp_t pri) +{ + struct sk_buff *skb; + int hdr = 64; + + if ((skb = alloc_skb(size + hdr, pri)) == NULL) + return NULL; + + skb->protocol = htons(ETH_P_DNA_RT); + skb->pkt_type = PACKET_OUTGOING; + + if (sk) + skb_set_owner_w(skb, sk); + + skb_reserve(skb, hdr); + + return skb; +} + +/* + * Calculate persist timer based upon the smoothed round + * trip time and the variance. Backoff according to the + * nsp_backoff[] array. + */ +unsigned long dn_nsp_persist(struct sock *sk) +{ + struct dn_scp *scp = DN_SK(sk); + + unsigned long t = ((scp->nsp_srtt >> 2) + scp->nsp_rttvar) >> 1; + + t *= nsp_backoff[scp->nsp_rxtshift]; + + if (t < HZ) t = HZ; + if (t > (600*HZ)) t = (600*HZ); + + if (scp->nsp_rxtshift < NSP_MAXRXTSHIFT) + scp->nsp_rxtshift++; + + /* printk(KERN_DEBUG "rxtshift %lu, t=%lu\n", scp->nsp_rxtshift, t); */ + + return t; +} + +/* + * This is called each time we get an estimate for the rtt + * on the link. + */ +static void dn_nsp_rtt(struct sock *sk, long rtt) +{ + struct dn_scp *scp = DN_SK(sk); + long srtt = (long)scp->nsp_srtt; + long rttvar = (long)scp->nsp_rttvar; + long delta; + + /* + * If the jiffies clock flips over in the middle of timestamp + * gathering this value might turn out negative, so we make sure + * that is it always positive here. + */ + if (rtt < 0) + rtt = -rtt; + /* + * Add new rtt to smoothed average + */ + delta = ((rtt << 3) - srtt); + srtt += (delta >> 3); + if (srtt >= 1) + scp->nsp_srtt = (unsigned long)srtt; + else + scp->nsp_srtt = 1; + + /* + * Add new rtt varience to smoothed varience + */ + delta >>= 1; + rttvar += ((((delta>0)?(delta):(-delta)) - rttvar) >> 2); + if (rttvar >= 1) + scp->nsp_rttvar = (unsigned long)rttvar; + else + scp->nsp_rttvar = 1; + + /* printk(KERN_DEBUG "srtt=%lu rttvar=%lu\n", scp->nsp_srtt, scp->nsp_rttvar); */ +} + +/** + * dn_nsp_clone_and_send - Send a data packet by cloning it + * @skb: The packet to clone and transmit + * @gfp: memory allocation flag + * + * Clone a queued data or other data packet and transmit it. + * + * Returns: The number of times the packet has been sent previously + */ +static inline unsigned int dn_nsp_clone_and_send(struct sk_buff *skb, + gfp_t gfp) +{ + struct dn_skb_cb *cb = DN_SKB_CB(skb); + struct sk_buff *skb2; + int ret = 0; + + if ((skb2 = skb_clone(skb, gfp)) != NULL) { + ret = cb->xmit_count; + cb->xmit_count++; + cb->stamp = jiffies; + skb2->sk = skb->sk; + dn_nsp_send(skb2); + } + + return ret; +} + +/** + * dn_nsp_output - Try and send something from socket queues + * @sk: The socket whose queues are to be investigated + * + * Try and send the packet on the end of the data and other data queues. + * Other data gets priority over data, and if we retransmit a packet we + * reduce the window by dividing it in two. + * + */ +void dn_nsp_output(struct sock *sk) +{ + struct dn_scp *scp = DN_SK(sk); + struct sk_buff *skb; + unsigned int reduce_win = 0; + + /* + * First we check for otherdata/linkservice messages + */ + if ((skb = skb_peek(&scp->other_xmit_queue)) != NULL) + reduce_win = dn_nsp_clone_and_send(skb, GFP_ATOMIC); + + /* + * If we may not send any data, we don't. + * If we are still trying to get some other data down the + * channel, we don't try and send any data. + */ + if (reduce_win || (scp->flowrem_sw != DN_SEND)) + goto recalc_window; + + if ((skb = skb_peek(&scp->data_xmit_queue)) != NULL) + reduce_win = dn_nsp_clone_and_send(skb, GFP_ATOMIC); + + /* + * If we've sent any frame more than once, we cut the + * send window size in half. There is always a minimum + * window size of one available. + */ +recalc_window: + if (reduce_win) { + scp->snd_window >>= 1; + if (scp->snd_window < NSP_MIN_WINDOW) + scp->snd_window = NSP_MIN_WINDOW; + } +} + +int dn_nsp_xmit_timeout(struct sock *sk) +{ + struct dn_scp *scp = DN_SK(sk); + + dn_nsp_output(sk); + + if (!skb_queue_empty(&scp->data_xmit_queue) || + !skb_queue_empty(&scp->other_xmit_queue)) + scp->persist = dn_nsp_persist(sk); + + return 0; +} + +static inline __le16 *dn_mk_common_header(struct dn_scp *scp, struct sk_buff *skb, unsigned char msgflag, int len) +{ + unsigned char *ptr = skb_push(skb, len); + + BUG_ON(len < 5); + + *ptr++ = msgflag; + *((__le16 *)ptr) = scp->addrrem; + ptr += 2; + *((__le16 *)ptr) = scp->addrloc; + ptr += 2; + return (__le16 __force *)ptr; +} + +static __le16 *dn_mk_ack_header(struct sock *sk, struct sk_buff *skb, unsigned char msgflag, int hlen, int other) +{ + struct dn_scp *scp = DN_SK(sk); + unsigned short acknum = scp->numdat_rcv & 0x0FFF; + unsigned short ackcrs = scp->numoth_rcv & 0x0FFF; + __le16 *ptr; + + BUG_ON(hlen < 9); + + scp->ackxmt_dat = acknum; + scp->ackxmt_oth = ackcrs; + acknum |= 0x8000; + ackcrs |= 0x8000; + + /* If this is an "other data/ack" message, swap acknum and ackcrs */ + if (other) { + unsigned short tmp = acknum; + acknum = ackcrs; + ackcrs = tmp; + } + + /* Set "cross subchannel" bit in ackcrs */ + ackcrs |= 0x2000; + + ptr = dn_mk_common_header(scp, skb, msgflag, hlen); + + *ptr++ = cpu_to_le16(acknum); + *ptr++ = cpu_to_le16(ackcrs); + + return ptr; +} + +static __le16 *dn_nsp_mk_data_header(struct sock *sk, struct sk_buff *skb, int oth) +{ + struct dn_scp *scp = DN_SK(sk); + struct dn_skb_cb *cb = DN_SKB_CB(skb); + __le16 *ptr = dn_mk_ack_header(sk, skb, cb->nsp_flags, 11, oth); + + if (unlikely(oth)) { + cb->segnum = scp->numoth; + seq_add(&scp->numoth, 1); + } else { + cb->segnum = scp->numdat; + seq_add(&scp->numdat, 1); + } + *(ptr++) = cpu_to_le16(cb->segnum); + + return ptr; +} + +void dn_nsp_queue_xmit(struct sock *sk, struct sk_buff *skb, + gfp_t gfp, int oth) +{ + struct dn_scp *scp = DN_SK(sk); + struct dn_skb_cb *cb = DN_SKB_CB(skb); + unsigned long t = ((scp->nsp_srtt >> 2) + scp->nsp_rttvar) >> 1; + + cb->xmit_count = 0; + dn_nsp_mk_data_header(sk, skb, oth); + + /* + * Slow start: If we have been idle for more than + * one RTT, then reset window to min size. + */ + if ((jiffies - scp->stamp) > t) + scp->snd_window = NSP_MIN_WINDOW; + + if (oth) + skb_queue_tail(&scp->other_xmit_queue, skb); + else + skb_queue_tail(&scp->data_xmit_queue, skb); + + if (scp->flowrem_sw != DN_SEND) + return; + + dn_nsp_clone_and_send(skb, gfp); +} + + +int dn_nsp_check_xmit_queue(struct sock *sk, struct sk_buff *skb, struct sk_buff_head *q, unsigned short acknum) +{ + struct dn_skb_cb *cb = DN_SKB_CB(skb); + struct dn_scp *scp = DN_SK(sk); + struct sk_buff *skb2, *n, *ack = NULL; + int wakeup = 0; + int try_retrans = 0; + unsigned long reftime = cb->stamp; + unsigned long pkttime; + unsigned short xmit_count; + unsigned short segnum; + + skb_queue_walk_safe(q, skb2, n) { + struct dn_skb_cb *cb2 = DN_SKB_CB(skb2); + + if (dn_before_or_equal(cb2->segnum, acknum)) + ack = skb2; + + /* printk(KERN_DEBUG "ack: %s %04x %04x\n", ack ? "ACK" : "SKIP", (int)cb2->segnum, (int)acknum); */ + + if (ack == NULL) + continue; + + /* printk(KERN_DEBUG "check_xmit_queue: %04x, %d\n", acknum, cb2->xmit_count); */ + + /* Does _last_ packet acked have xmit_count > 1 */ + try_retrans = 0; + /* Remember to wake up the sending process */ + wakeup = 1; + /* Keep various statistics */ + pkttime = cb2->stamp; + xmit_count = cb2->xmit_count; + segnum = cb2->segnum; + /* Remove and drop ack'ed packet */ + skb_unlink(ack, q); + kfree_skb(ack); + ack = NULL; + + /* + * We don't expect to see acknowledgements for packets we + * haven't sent yet. + */ + WARN_ON(xmit_count == 0); + + /* + * If the packet has only been sent once, we can use it + * to calculate the RTT and also open the window a little + * further. + */ + if (xmit_count == 1) { + if (dn_equal(segnum, acknum)) + dn_nsp_rtt(sk, (long)(pkttime - reftime)); + + if (scp->snd_window < scp->max_window) + scp->snd_window++; + } + + /* + * Packet has been sent more than once. If this is the last + * packet to be acknowledged then we want to send the next + * packet in the send queue again (assumes the remote host does + * go-back-N error control). + */ + if (xmit_count > 1) + try_retrans = 1; + } + + if (try_retrans) + dn_nsp_output(sk); + + return wakeup; +} + +void dn_nsp_send_data_ack(struct sock *sk) +{ + struct sk_buff *skb = NULL; + + if ((skb = dn_alloc_skb(sk, 9, GFP_ATOMIC)) == NULL) + return; + + skb_reserve(skb, 9); + dn_mk_ack_header(sk, skb, 0x04, 9, 0); + dn_nsp_send(skb); +} + +void dn_nsp_send_oth_ack(struct sock *sk) +{ + struct sk_buff *skb = NULL; + + if ((skb = dn_alloc_skb(sk, 9, GFP_ATOMIC)) == NULL) + return; + + skb_reserve(skb, 9); + dn_mk_ack_header(sk, skb, 0x14, 9, 1); + dn_nsp_send(skb); +} + + +void dn_send_conn_ack (struct sock *sk) +{ + struct dn_scp *scp = DN_SK(sk); + struct sk_buff *skb = NULL; + struct nsp_conn_ack_msg *msg; + + if ((skb = dn_alloc_skb(sk, 3, sk->sk_allocation)) == NULL) + return; + + msg = (struct nsp_conn_ack_msg *)skb_put(skb, 3); + msg->msgflg = 0x24; + msg->dstaddr = scp->addrrem; + + dn_nsp_send(skb); +} + +void dn_nsp_delayed_ack(struct sock *sk) +{ + struct dn_scp *scp = DN_SK(sk); + + if (scp->ackxmt_oth != scp->numoth_rcv) + dn_nsp_send_oth_ack(sk); + + if (scp->ackxmt_dat != scp->numdat_rcv) + dn_nsp_send_data_ack(sk); +} + +static int dn_nsp_retrans_conn_conf(struct sock *sk) +{ + struct dn_scp *scp = DN_SK(sk); + + if (scp->state == DN_CC) + dn_send_conn_conf(sk, GFP_ATOMIC); + + return 0; +} + +void dn_send_conn_conf(struct sock *sk, gfp_t gfp) +{ + struct dn_scp *scp = DN_SK(sk); + struct sk_buff *skb = NULL; + struct nsp_conn_init_msg *msg; + __u8 len = (__u8)le16_to_cpu(scp->conndata_out.opt_optl); + + if ((skb = dn_alloc_skb(sk, 50 + len, gfp)) == NULL) + return; + + msg = (struct nsp_conn_init_msg *)skb_put(skb, sizeof(*msg)); + msg->msgflg = 0x28; + msg->dstaddr = scp->addrrem; + msg->srcaddr = scp->addrloc; + msg->services = scp->services_loc; + msg->info = scp->info_loc; + msg->segsize = cpu_to_le16(scp->segsize_loc); + + *skb_put(skb,1) = len; + + if (len > 0) + memcpy(skb_put(skb, len), scp->conndata_out.opt_data, len); + + + dn_nsp_send(skb); + + scp->persist = dn_nsp_persist(sk); + scp->persist_fxn = dn_nsp_retrans_conn_conf; +} + + +static __inline__ void dn_nsp_do_disc(struct sock *sk, unsigned char msgflg, + unsigned short reason, gfp_t gfp, + struct dst_entry *dst, + int ddl, unsigned char *dd, __le16 rem, __le16 loc) +{ + struct sk_buff *skb = NULL; + int size = 7 + ddl + ((msgflg == NSP_DISCINIT) ? 1 : 0); + unsigned char *msg; + + if ((dst == NULL) || (rem == 0)) { + net_dbg_ratelimited("DECnet: dn_nsp_do_disc: BUG! Please report this to SteveW@ACM.org rem=%u dst=%p\n", + le16_to_cpu(rem), dst); + return; + } + + if ((skb = dn_alloc_skb(sk, size, gfp)) == NULL) + return; + + msg = skb_put(skb, size); + *msg++ = msgflg; + *(__le16 *)msg = rem; + msg += 2; + *(__le16 *)msg = loc; + msg += 2; + *(__le16 *)msg = cpu_to_le16(reason); + msg += 2; + if (msgflg == NSP_DISCINIT) + *msg++ = ddl; + + if (ddl) { + memcpy(msg, dd, ddl); + } + + /* + * This doesn't go via the dn_nsp_send() function since we need + * to be able to send disc packets out which have no socket + * associations. + */ + skb_dst_set(skb, dst_clone(dst)); + dst_output(skb); +} + + +void dn_nsp_send_disc(struct sock *sk, unsigned char msgflg, + unsigned short reason, gfp_t gfp) +{ + struct dn_scp *scp = DN_SK(sk); + int ddl = 0; + + if (msgflg == NSP_DISCINIT) + ddl = le16_to_cpu(scp->discdata_out.opt_optl); + + if (reason == 0) + reason = le16_to_cpu(scp->discdata_out.opt_status); + + dn_nsp_do_disc(sk, msgflg, reason, gfp, __sk_dst_get(sk), ddl, + scp->discdata_out.opt_data, scp->addrrem, scp->addrloc); +} + + +void dn_nsp_return_disc(struct sk_buff *skb, unsigned char msgflg, + unsigned short reason) +{ + struct dn_skb_cb *cb = DN_SKB_CB(skb); + int ddl = 0; + gfp_t gfp = GFP_ATOMIC; + + dn_nsp_do_disc(NULL, msgflg, reason, gfp, skb_dst(skb), ddl, + NULL, cb->src_port, cb->dst_port); +} + + +void dn_nsp_send_link(struct sock *sk, unsigned char lsflags, char fcval) +{ + struct dn_scp *scp = DN_SK(sk); + struct sk_buff *skb; + unsigned char *ptr; + gfp_t gfp = GFP_ATOMIC; + + if ((skb = dn_alloc_skb(sk, DN_MAX_NSP_DATA_HEADER + 2, gfp)) == NULL) + return; + + skb_reserve(skb, DN_MAX_NSP_DATA_HEADER); + ptr = skb_put(skb, 2); + DN_SKB_CB(skb)->nsp_flags = 0x10; + *ptr++ = lsflags; + *ptr = fcval; + + dn_nsp_queue_xmit(sk, skb, gfp, 1); + + scp->persist = dn_nsp_persist(sk); + scp->persist_fxn = dn_nsp_xmit_timeout; +} + +static int dn_nsp_retrans_conninit(struct sock *sk) +{ + struct dn_scp *scp = DN_SK(sk); + + if (scp->state == DN_CI) + dn_nsp_send_conninit(sk, NSP_RCI); + + return 0; +} + +void dn_nsp_send_conninit(struct sock *sk, unsigned char msgflg) +{ + struct dn_scp *scp = DN_SK(sk); + struct nsp_conn_init_msg *msg; + unsigned char aux; + unsigned char menuver; + struct dn_skb_cb *cb; + unsigned char type = 1; + gfp_t allocation = (msgflg == NSP_CI) ? sk->sk_allocation : GFP_ATOMIC; + struct sk_buff *skb = dn_alloc_skb(sk, 200, allocation); + + if (!skb) + return; + + cb = DN_SKB_CB(skb); + msg = (struct nsp_conn_init_msg *)skb_put(skb,sizeof(*msg)); + + msg->msgflg = msgflg; + msg->dstaddr = 0x0000; /* Remote Node will assign it*/ + + msg->srcaddr = scp->addrloc; + msg->services = scp->services_loc; /* Requested flow control */ + msg->info = scp->info_loc; /* Version Number */ + msg->segsize = cpu_to_le16(scp->segsize_loc); /* Max segment size */ + + if (scp->peer.sdn_objnum) + type = 0; + + skb_put(skb, dn_sockaddr2username(&scp->peer, + skb_tail_pointer(skb), type)); + skb_put(skb, dn_sockaddr2username(&scp->addr, + skb_tail_pointer(skb), 2)); + + menuver = DN_MENUVER_ACC | DN_MENUVER_USR; + if (scp->peer.sdn_flags & SDF_PROXY) + menuver |= DN_MENUVER_PRX; + if (scp->peer.sdn_flags & SDF_UICPROXY) + menuver |= DN_MENUVER_UIC; + + *skb_put(skb, 1) = menuver; /* Menu Version */ + + aux = scp->accessdata.acc_userl; + *skb_put(skb, 1) = aux; + if (aux > 0) + memcpy(skb_put(skb, aux), scp->accessdata.acc_user, aux); + + aux = scp->accessdata.acc_passl; + *skb_put(skb, 1) = aux; + if (aux > 0) + memcpy(skb_put(skb, aux), scp->accessdata.acc_pass, aux); + + aux = scp->accessdata.acc_accl; + *skb_put(skb, 1) = aux; + if (aux > 0) + memcpy(skb_put(skb, aux), scp->accessdata.acc_acc, aux); + + aux = (__u8)le16_to_cpu(scp->conndata_out.opt_optl); + *skb_put(skb, 1) = aux; + if (aux > 0) + memcpy(skb_put(skb, aux), scp->conndata_out.opt_data, aux); + + scp->persist = dn_nsp_persist(sk); + scp->persist_fxn = dn_nsp_retrans_conninit; + + cb->rt_flags = DN_RT_F_RQR; + + dn_nsp_send(skb); +} + diff --git a/net/decnet/dn_route.c b/net/decnet/dn_route.c new file mode 100644 index 000000000..03227ffd1 --- /dev/null +++ b/net/decnet/dn_route.c @@ -0,0 +1,1939 @@ +/* + * DECnet An implementation of the DECnet protocol suite for the LINUX + * operating system. DECnet is implemented using the BSD Socket + * interface as the means of communication with the user level. + * + * DECnet Routing Functions (Endnode and Router) + * + * Authors: Steve Whitehouse <SteveW@ACM.org> + * Eduardo Marcelo Serrat <emserrat@geocities.com> + * + * Changes: + * Steve Whitehouse : Fixes to allow "intra-ethernet" and + * "return-to-sender" bits on outgoing + * packets. + * Steve Whitehouse : Timeouts for cached routes. + * Steve Whitehouse : Use dst cache for input routes too. + * Steve Whitehouse : Fixed error values in dn_send_skb. + * Steve Whitehouse : Rework routing functions to better fit + * DECnet routing design + * Alexey Kuznetsov : New SMP locking + * Steve Whitehouse : More SMP locking changes & dn_cache_dump() + * Steve Whitehouse : Prerouting NF hook, now really is prerouting. + * Fixed possible skb leak in rtnetlink funcs. + * Steve Whitehouse : Dave Miller's dynamic hash table sizing and + * Alexey Kuznetsov's finer grained locking + * from ipv4/route.c. + * Steve Whitehouse : Routing is now starting to look like a + * sensible set of code now, mainly due to + * my copying the IPv4 routing code. The + * hooks here are modified and will continue + * to evolve for a while. + * Steve Whitehouse : Real SMP at last :-) Also new netfilter + * stuff. Look out raw sockets your days + * are numbered! + * Steve Whitehouse : Added return-to-sender functions. Added + * backlog congestion level return codes. + * Steve Whitehouse : Fixed bug where routes were set up with + * no ref count on net devices. + * Steve Whitehouse : RCU for the route cache + * Steve Whitehouse : Preparations for the flow cache + * Steve Whitehouse : Prepare for nonlinear skbs + */ + +/****************************************************************************** + (c) 1995-1998 E.M. Serrat emserrat@geocities.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. +*******************************************************************************/ + +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/socket.h> +#include <linux/in.h> +#include <linux/kernel.h> +#include <linux/sockios.h> +#include <linux/net.h> +#include <linux/netdevice.h> +#include <linux/inet.h> +#include <linux/route.h> +#include <linux/in_route.h> +#include <linux/slab.h> +#include <net/sock.h> +#include <linux/mm.h> +#include <linux/proc_fs.h> +#include <linux/seq_file.h> +#include <linux/init.h> +#include <linux/rtnetlink.h> +#include <linux/string.h> +#include <linux/netfilter_decnet.h> +#include <linux/rcupdate.h> +#include <linux/times.h> +#include <linux/export.h> +#include <asm/errno.h> +#include <net/net_namespace.h> +#include <net/netlink.h> +#include <net/neighbour.h> +#include <net/dst.h> +#include <net/flow.h> +#include <net/fib_rules.h> +#include <net/dn.h> +#include <net/dn_dev.h> +#include <net/dn_nsp.h> +#include <net/dn_route.h> +#include <net/dn_neigh.h> +#include <net/dn_fib.h> + +struct dn_rt_hash_bucket +{ + struct dn_route __rcu *chain; + spinlock_t lock; +}; + +extern struct neigh_table dn_neigh_table; + + +static unsigned char dn_hiord_addr[6] = {0xAA,0x00,0x04,0x00,0x00,0x00}; + +static const int dn_rt_min_delay = 2 * HZ; +static const int dn_rt_max_delay = 10 * HZ; +static const int dn_rt_mtu_expires = 10 * 60 * HZ; + +static unsigned long dn_rt_deadline; + +static int dn_dst_gc(struct dst_ops *ops); +static struct dst_entry *dn_dst_check(struct dst_entry *, __u32); +static unsigned int dn_dst_default_advmss(const struct dst_entry *dst); +static unsigned int dn_dst_mtu(const struct dst_entry *dst); +static void dn_dst_destroy(struct dst_entry *); +static void dn_dst_ifdown(struct dst_entry *, struct net_device *dev, int how); +static struct dst_entry *dn_dst_negative_advice(struct dst_entry *); +static void dn_dst_link_failure(struct sk_buff *); +static void dn_dst_update_pmtu(struct dst_entry *dst, struct sock *sk, + struct sk_buff *skb , u32 mtu); +static void dn_dst_redirect(struct dst_entry *dst, struct sock *sk, + struct sk_buff *skb); +static struct neighbour *dn_dst_neigh_lookup(const struct dst_entry *dst, + struct sk_buff *skb, + const void *daddr); +static int dn_route_input(struct sk_buff *); +static void dn_run_flush(unsigned long dummy); + +static struct dn_rt_hash_bucket *dn_rt_hash_table; +static unsigned int dn_rt_hash_mask; + +static struct timer_list dn_route_timer; +static DEFINE_TIMER(dn_rt_flush_timer, dn_run_flush, 0, 0); +int decnet_dst_gc_interval = 2; + +static struct dst_ops dn_dst_ops = { + .family = PF_DECnet, + .gc_thresh = 128, + .gc = dn_dst_gc, + .check = dn_dst_check, + .default_advmss = dn_dst_default_advmss, + .mtu = dn_dst_mtu, + .cow_metrics = dst_cow_metrics_generic, + .destroy = dn_dst_destroy, + .ifdown = dn_dst_ifdown, + .negative_advice = dn_dst_negative_advice, + .link_failure = dn_dst_link_failure, + .update_pmtu = dn_dst_update_pmtu, + .redirect = dn_dst_redirect, + .neigh_lookup = dn_dst_neigh_lookup, +}; + +static void dn_dst_destroy(struct dst_entry *dst) +{ + struct dn_route *rt = (struct dn_route *) dst; + + if (rt->n) + neigh_release(rt->n); + dst_destroy_metrics_generic(dst); +} + +static void dn_dst_ifdown(struct dst_entry *dst, struct net_device *dev, int how) +{ + if (how) { + struct dn_route *rt = (struct dn_route *) dst; + struct neighbour *n = rt->n; + + if (n && n->dev == dev) { + n->dev = dev_net(dev)->loopback_dev; + dev_hold(n->dev); + dev_put(dev); + } + } +} + +static __inline__ unsigned int dn_hash(__le16 src, __le16 dst) +{ + __u16 tmp = (__u16 __force)(src ^ dst); + tmp ^= (tmp >> 3); + tmp ^= (tmp >> 5); + tmp ^= (tmp >> 10); + return dn_rt_hash_mask & (unsigned int)tmp; +} + +static inline void dnrt_free(struct dn_route *rt) +{ + call_rcu_bh(&rt->dst.rcu_head, dst_rcu_free); +} + +static inline void dnrt_drop(struct dn_route *rt) +{ + dst_release(&rt->dst); + call_rcu_bh(&rt->dst.rcu_head, dst_rcu_free); +} + +static void dn_dst_check_expire(unsigned long dummy) +{ + int i; + struct dn_route *rt; + struct dn_route __rcu **rtp; + unsigned long now = jiffies; + unsigned long expire = 120 * HZ; + + for (i = 0; i <= dn_rt_hash_mask; i++) { + rtp = &dn_rt_hash_table[i].chain; + + spin_lock(&dn_rt_hash_table[i].lock); + while ((rt = rcu_dereference_protected(*rtp, + lockdep_is_held(&dn_rt_hash_table[i].lock))) != NULL) { + if (atomic_read(&rt->dst.__refcnt) || + (now - rt->dst.lastuse) < expire) { + rtp = &rt->dst.dn_next; + continue; + } + *rtp = rt->dst.dn_next; + rt->dst.dn_next = NULL; + dnrt_free(rt); + } + spin_unlock(&dn_rt_hash_table[i].lock); + + if ((jiffies - now) > 0) + break; + } + + mod_timer(&dn_route_timer, now + decnet_dst_gc_interval * HZ); +} + +static int dn_dst_gc(struct dst_ops *ops) +{ + struct dn_route *rt; + struct dn_route __rcu **rtp; + int i; + unsigned long now = jiffies; + unsigned long expire = 10 * HZ; + + for (i = 0; i <= dn_rt_hash_mask; i++) { + + spin_lock_bh(&dn_rt_hash_table[i].lock); + rtp = &dn_rt_hash_table[i].chain; + + while ((rt = rcu_dereference_protected(*rtp, + lockdep_is_held(&dn_rt_hash_table[i].lock))) != NULL) { + if (atomic_read(&rt->dst.__refcnt) || + (now - rt->dst.lastuse) < expire) { + rtp = &rt->dst.dn_next; + continue; + } + *rtp = rt->dst.dn_next; + rt->dst.dn_next = NULL; + dnrt_drop(rt); + break; + } + spin_unlock_bh(&dn_rt_hash_table[i].lock); + } + + return 0; +} + +/* + * The decnet standards don't impose a particular minimum mtu, what they + * do insist on is that the routing layer accepts a datagram of at least + * 230 bytes long. Here we have to subtract the routing header length from + * 230 to get the minimum acceptable mtu. If there is no neighbour, then we + * assume the worst and use a long header size. + * + * We update both the mtu and the advertised mss (i.e. the segment size we + * advertise to the other end). + */ +static void dn_dst_update_pmtu(struct dst_entry *dst, struct sock *sk, + struct sk_buff *skb, u32 mtu) +{ + struct dn_route *rt = (struct dn_route *) dst; + struct neighbour *n = rt->n; + u32 min_mtu = 230; + struct dn_dev *dn; + + dn = n ? rcu_dereference_raw(n->dev->dn_ptr) : NULL; + + if (dn && dn->use_long == 0) + min_mtu -= 6; + else + min_mtu -= 21; + + if (dst_metric(dst, RTAX_MTU) > mtu && mtu >= min_mtu) { + if (!(dst_metric_locked(dst, RTAX_MTU))) { + dst_metric_set(dst, RTAX_MTU, mtu); + dst_set_expires(dst, dn_rt_mtu_expires); + } + if (!(dst_metric_locked(dst, RTAX_ADVMSS))) { + u32 mss = mtu - DN_MAX_NSP_DATA_HEADER; + u32 existing_mss = dst_metric_raw(dst, RTAX_ADVMSS); + if (!existing_mss || existing_mss > mss) + dst_metric_set(dst, RTAX_ADVMSS, mss); + } + } +} + +static void dn_dst_redirect(struct dst_entry *dst, struct sock *sk, + struct sk_buff *skb) +{ +} + +/* + * When a route has been marked obsolete. (e.g. routing cache flush) + */ +static struct dst_entry *dn_dst_check(struct dst_entry *dst, __u32 cookie) +{ + return NULL; +} + +static struct dst_entry *dn_dst_negative_advice(struct dst_entry *dst) +{ + dst_release(dst); + return NULL; +} + +static void dn_dst_link_failure(struct sk_buff *skb) +{ +} + +static inline int compare_keys(struct flowidn *fl1, struct flowidn *fl2) +{ + return ((fl1->daddr ^ fl2->daddr) | + (fl1->saddr ^ fl2->saddr) | + (fl1->flowidn_mark ^ fl2->flowidn_mark) | + (fl1->flowidn_scope ^ fl2->flowidn_scope) | + (fl1->flowidn_oif ^ fl2->flowidn_oif) | + (fl1->flowidn_iif ^ fl2->flowidn_iif)) == 0; +} + +static int dn_insert_route(struct dn_route *rt, unsigned int hash, struct dn_route **rp) +{ + struct dn_route *rth; + struct dn_route __rcu **rthp; + unsigned long now = jiffies; + + rthp = &dn_rt_hash_table[hash].chain; + + spin_lock_bh(&dn_rt_hash_table[hash].lock); + while ((rth = rcu_dereference_protected(*rthp, + lockdep_is_held(&dn_rt_hash_table[hash].lock))) != NULL) { + if (compare_keys(&rth->fld, &rt->fld)) { + /* Put it first */ + *rthp = rth->dst.dn_next; + rcu_assign_pointer(rth->dst.dn_next, + dn_rt_hash_table[hash].chain); + rcu_assign_pointer(dn_rt_hash_table[hash].chain, rth); + + dst_use(&rth->dst, now); + spin_unlock_bh(&dn_rt_hash_table[hash].lock); + + dnrt_drop(rt); + *rp = rth; + return 0; + } + rthp = &rth->dst.dn_next; + } + + rcu_assign_pointer(rt->dst.dn_next, dn_rt_hash_table[hash].chain); + rcu_assign_pointer(dn_rt_hash_table[hash].chain, rt); + + dst_use(&rt->dst, now); + spin_unlock_bh(&dn_rt_hash_table[hash].lock); + *rp = rt; + return 0; +} + +static void dn_run_flush(unsigned long dummy) +{ + int i; + struct dn_route *rt, *next; + + for (i = 0; i < dn_rt_hash_mask; i++) { + spin_lock_bh(&dn_rt_hash_table[i].lock); + + if ((rt = xchg((struct dn_route **)&dn_rt_hash_table[i].chain, NULL)) == NULL) + goto nothing_to_declare; + + for(; rt; rt = next) { + next = rcu_dereference_raw(rt->dst.dn_next); + RCU_INIT_POINTER(rt->dst.dn_next, NULL); + dst_free((struct dst_entry *)rt); + } + +nothing_to_declare: + spin_unlock_bh(&dn_rt_hash_table[i].lock); + } +} + +static DEFINE_SPINLOCK(dn_rt_flush_lock); + +void dn_rt_cache_flush(int delay) +{ + unsigned long now = jiffies; + int user_mode = !in_interrupt(); + + if (delay < 0) + delay = dn_rt_min_delay; + + spin_lock_bh(&dn_rt_flush_lock); + + if (del_timer(&dn_rt_flush_timer) && delay > 0 && dn_rt_deadline) { + long tmo = (long)(dn_rt_deadline - now); + + if (user_mode && tmo < dn_rt_max_delay - dn_rt_min_delay) + tmo = 0; + + if (delay > tmo) + delay = tmo; + } + + if (delay <= 0) { + spin_unlock_bh(&dn_rt_flush_lock); + dn_run_flush(0); + return; + } + + if (dn_rt_deadline == 0) + dn_rt_deadline = now + dn_rt_max_delay; + + dn_rt_flush_timer.expires = now + delay; + add_timer(&dn_rt_flush_timer); + spin_unlock_bh(&dn_rt_flush_lock); +} + +/** + * dn_return_short - Return a short packet to its sender + * @skb: The packet to return + * + */ +static int dn_return_short(struct sk_buff *skb) +{ + struct dn_skb_cb *cb; + unsigned char *ptr; + __le16 *src; + __le16 *dst; + + /* Add back headers */ + skb_push(skb, skb->data - skb_network_header(skb)); + + if ((skb = skb_unshare(skb, GFP_ATOMIC)) == NULL) + return NET_RX_DROP; + + cb = DN_SKB_CB(skb); + /* Skip packet length and point to flags */ + ptr = skb->data + 2; + *ptr++ = (cb->rt_flags & ~DN_RT_F_RQR) | DN_RT_F_RTS; + + dst = (__le16 *)ptr; + ptr += 2; + src = (__le16 *)ptr; + ptr += 2; + *ptr = 0; /* Zero hop count */ + + swap(*src, *dst); + + skb->pkt_type = PACKET_OUTGOING; + dn_rt_finish_output(skb, NULL, NULL); + return NET_RX_SUCCESS; +} + +/** + * dn_return_long - Return a long packet to its sender + * @skb: The long format packet to return + * + */ +static int dn_return_long(struct sk_buff *skb) +{ + struct dn_skb_cb *cb; + unsigned char *ptr; + unsigned char *src_addr, *dst_addr; + unsigned char tmp[ETH_ALEN]; + + /* Add back all headers */ + skb_push(skb, skb->data - skb_network_header(skb)); + + if ((skb = skb_unshare(skb, GFP_ATOMIC)) == NULL) + return NET_RX_DROP; + + cb = DN_SKB_CB(skb); + /* Ignore packet length and point to flags */ + ptr = skb->data + 2; + + /* Skip padding */ + if (*ptr & DN_RT_F_PF) { + char padlen = (*ptr & ~DN_RT_F_PF); + ptr += padlen; + } + + *ptr++ = (cb->rt_flags & ~DN_RT_F_RQR) | DN_RT_F_RTS; + ptr += 2; + dst_addr = ptr; + ptr += 8; + src_addr = ptr; + ptr += 6; + *ptr = 0; /* Zero hop count */ + + /* Swap source and destination */ + memcpy(tmp, src_addr, ETH_ALEN); + memcpy(src_addr, dst_addr, ETH_ALEN); + memcpy(dst_addr, tmp, ETH_ALEN); + + skb->pkt_type = PACKET_OUTGOING; + dn_rt_finish_output(skb, dst_addr, src_addr); + return NET_RX_SUCCESS; +} + +/** + * dn_route_rx_packet - Try and find a route for an incoming packet + * @skb: The packet to find a route for + * + * Returns: result of input function if route is found, error code otherwise + */ +static int dn_route_rx_packet(struct sock *sk, struct sk_buff *skb) +{ + struct dn_skb_cb *cb; + int err; + + if ((err = dn_route_input(skb)) == 0) + return dst_input(skb); + + cb = DN_SKB_CB(skb); + if (decnet_debug_level & 4) { + char *devname = skb->dev ? skb->dev->name : "???"; + + printk(KERN_DEBUG + "DECnet: dn_route_rx_packet: rt_flags=0x%02x dev=%s len=%d src=0x%04hx dst=0x%04hx err=%d type=%d\n", + (int)cb->rt_flags, devname, skb->len, + le16_to_cpu(cb->src), le16_to_cpu(cb->dst), + err, skb->pkt_type); + } + + if ((skb->pkt_type == PACKET_HOST) && (cb->rt_flags & DN_RT_F_RQR)) { + switch (cb->rt_flags & DN_RT_PKT_MSK) { + case DN_RT_PKT_SHORT: + return dn_return_short(skb); + case DN_RT_PKT_LONG: + return dn_return_long(skb); + } + } + + kfree_skb(skb); + return NET_RX_DROP; +} + +static int dn_route_rx_long(struct sk_buff *skb) +{ + struct dn_skb_cb *cb = DN_SKB_CB(skb); + unsigned char *ptr = skb->data; + + if (!pskb_may_pull(skb, 21)) /* 20 for long header, 1 for shortest nsp */ + goto drop_it; + + skb_pull(skb, 20); + skb_reset_transport_header(skb); + + /* Destination info */ + ptr += 2; + cb->dst = dn_eth2dn(ptr); + if (memcmp(ptr, dn_hiord_addr, 4) != 0) + goto drop_it; + ptr += 6; + + + /* Source info */ + ptr += 2; + cb->src = dn_eth2dn(ptr); + if (memcmp(ptr, dn_hiord_addr, 4) != 0) + goto drop_it; + ptr += 6; + /* Other junk */ + ptr++; + cb->hops = *ptr++; /* Visit Count */ + + return NF_HOOK(NFPROTO_DECNET, NF_DN_PRE_ROUTING, NULL, skb, + skb->dev, NULL, + dn_route_rx_packet); + +drop_it: + kfree_skb(skb); + return NET_RX_DROP; +} + + + +static int dn_route_rx_short(struct sk_buff *skb) +{ + struct dn_skb_cb *cb = DN_SKB_CB(skb); + unsigned char *ptr = skb->data; + + if (!pskb_may_pull(skb, 6)) /* 5 for short header + 1 for shortest nsp */ + goto drop_it; + + skb_pull(skb, 5); + skb_reset_transport_header(skb); + + cb->dst = *(__le16 *)ptr; + ptr += 2; + cb->src = *(__le16 *)ptr; + ptr += 2; + cb->hops = *ptr & 0x3f; + + return NF_HOOK(NFPROTO_DECNET, NF_DN_PRE_ROUTING, NULL, skb, + skb->dev, NULL, + dn_route_rx_packet); + +drop_it: + kfree_skb(skb); + return NET_RX_DROP; +} + +static int dn_route_discard(struct sock *sk, struct sk_buff *skb) +{ + /* + * I know we drop the packet here, but thats considered success in + * this case + */ + kfree_skb(skb); + return NET_RX_SUCCESS; +} + +static int dn_route_ptp_hello(struct sock *sk, struct sk_buff *skb) +{ + dn_dev_hello(skb); + dn_neigh_pointopoint_hello(skb); + return NET_RX_SUCCESS; +} + +int dn_route_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev) +{ + struct dn_skb_cb *cb; + unsigned char flags = 0; + __u16 len = le16_to_cpu(*(__le16 *)skb->data); + struct dn_dev *dn = rcu_dereference(dev->dn_ptr); + unsigned char padlen = 0; + + if (!net_eq(dev_net(dev), &init_net)) + goto dump_it; + + if (dn == NULL) + goto dump_it; + + if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) + goto out; + + if (!pskb_may_pull(skb, 3)) + goto dump_it; + + skb_pull(skb, 2); + + if (len > skb->len) + goto dump_it; + + skb_trim(skb, len); + + flags = *skb->data; + + cb = DN_SKB_CB(skb); + cb->stamp = jiffies; + cb->iif = dev->ifindex; + + /* + * If we have padding, remove it. + */ + if (flags & DN_RT_F_PF) { + padlen = flags & ~DN_RT_F_PF; + if (!pskb_may_pull(skb, padlen + 1)) + goto dump_it; + skb_pull(skb, padlen); + flags = *skb->data; + } + + skb_reset_network_header(skb); + + /* + * Weed out future version DECnet + */ + if (flags & DN_RT_F_VER) + goto dump_it; + + cb->rt_flags = flags; + + if (decnet_debug_level & 1) + printk(KERN_DEBUG + "dn_route_rcv: got 0x%02x from %s [%d %d %d]\n", + (int)flags, (dev) ? dev->name : "???", len, skb->len, + padlen); + + if (flags & DN_RT_PKT_CNTL) { + if (unlikely(skb_linearize(skb))) + goto dump_it; + + switch (flags & DN_RT_CNTL_MSK) { + case DN_RT_PKT_INIT: + dn_dev_init_pkt(skb); + break; + case DN_RT_PKT_VERI: + dn_dev_veri_pkt(skb); + break; + } + + if (dn->parms.state != DN_DEV_S_RU) + goto dump_it; + + switch (flags & DN_RT_CNTL_MSK) { + case DN_RT_PKT_HELO: + return NF_HOOK(NFPROTO_DECNET, NF_DN_HELLO, + NULL, skb, skb->dev, NULL, + dn_route_ptp_hello); + + case DN_RT_PKT_L1RT: + case DN_RT_PKT_L2RT: + return NF_HOOK(NFPROTO_DECNET, NF_DN_ROUTE, + NULL, skb, skb->dev, NULL, + dn_route_discard); + case DN_RT_PKT_ERTH: + return NF_HOOK(NFPROTO_DECNET, NF_DN_HELLO, + NULL, skb, skb->dev, NULL, + dn_neigh_router_hello); + + case DN_RT_PKT_EEDH: + return NF_HOOK(NFPROTO_DECNET, NF_DN_HELLO, + NULL, skb, skb->dev, NULL, + dn_neigh_endnode_hello); + } + } else { + if (dn->parms.state != DN_DEV_S_RU) + goto dump_it; + + skb_pull(skb, 1); /* Pull flags */ + + switch (flags & DN_RT_PKT_MSK) { + case DN_RT_PKT_LONG: + return dn_route_rx_long(skb); + case DN_RT_PKT_SHORT: + return dn_route_rx_short(skb); + } + } + +dump_it: + kfree_skb(skb); +out: + return NET_RX_DROP; +} + +static int dn_output(struct sock *sk, struct sk_buff *skb) +{ + struct dst_entry *dst = skb_dst(skb); + struct dn_route *rt = (struct dn_route *)dst; + struct net_device *dev = dst->dev; + struct dn_skb_cb *cb = DN_SKB_CB(skb); + + int err = -EINVAL; + + if (rt->n == NULL) + goto error; + + skb->dev = dev; + + cb->src = rt->rt_saddr; + cb->dst = rt->rt_daddr; + + /* + * Always set the Intra-Ethernet bit on all outgoing packets + * originated on this node. Only valid flag from upper layers + * is return-to-sender-requested. Set hop count to 0 too. + */ + cb->rt_flags &= ~DN_RT_F_RQR; + cb->rt_flags |= DN_RT_F_IE; + cb->hops = 0; + + return NF_HOOK(NFPROTO_DECNET, NF_DN_LOCAL_OUT, sk, skb, + NULL, dev, + dn_to_neigh_output); + +error: + net_dbg_ratelimited("dn_output: This should not happen\n"); + + kfree_skb(skb); + + return err; +} + +static int dn_forward(struct sk_buff *skb) +{ + struct dn_skb_cb *cb = DN_SKB_CB(skb); + struct dst_entry *dst = skb_dst(skb); + struct dn_dev *dn_db = rcu_dereference(dst->dev->dn_ptr); + struct dn_route *rt; + int header_len; +#ifdef CONFIG_NETFILTER + struct net_device *dev = skb->dev; +#endif + + if (skb->pkt_type != PACKET_HOST) + goto drop; + + /* Ensure that we have enough space for headers */ + rt = (struct dn_route *)skb_dst(skb); + header_len = dn_db->use_long ? 21 : 6; + if (skb_cow(skb, LL_RESERVED_SPACE(rt->dst.dev)+header_len)) + goto drop; + + /* + * Hop count exceeded. + */ + if (++cb->hops > 30) + goto drop; + + skb->dev = rt->dst.dev; + + /* + * If packet goes out same interface it came in on, then set + * the Intra-Ethernet bit. This has no effect for short + * packets, so we don't need to test for them here. + */ + cb->rt_flags &= ~DN_RT_F_IE; + if (rt->rt_flags & RTCF_DOREDIRECT) + cb->rt_flags |= DN_RT_F_IE; + + return NF_HOOK(NFPROTO_DECNET, NF_DN_FORWARD, NULL, skb, + dev, skb->dev, + dn_to_neigh_output); + +drop: + kfree_skb(skb); + return NET_RX_DROP; +} + +/* + * Used to catch bugs. This should never normally get + * called. + */ +static int dn_rt_bug_sk(struct sock *sk, struct sk_buff *skb) +{ + struct dn_skb_cb *cb = DN_SKB_CB(skb); + + net_dbg_ratelimited("dn_rt_bug: skb from:%04x to:%04x\n", + le16_to_cpu(cb->src), le16_to_cpu(cb->dst)); + + kfree_skb(skb); + + return NET_RX_DROP; +} + +static int dn_rt_bug(struct sk_buff *skb) +{ + struct dn_skb_cb *cb = DN_SKB_CB(skb); + + net_dbg_ratelimited("dn_rt_bug: skb from:%04x to:%04x\n", + le16_to_cpu(cb->src), le16_to_cpu(cb->dst)); + + kfree_skb(skb); + + return NET_RX_DROP; +} + +static unsigned int dn_dst_default_advmss(const struct dst_entry *dst) +{ + return dn_mss_from_pmtu(dst->dev, dst_mtu(dst)); +} + +static unsigned int dn_dst_mtu(const struct dst_entry *dst) +{ + unsigned int mtu = dst_metric_raw(dst, RTAX_MTU); + + return mtu ? : dst->dev->mtu; +} + +static struct neighbour *dn_dst_neigh_lookup(const struct dst_entry *dst, + struct sk_buff *skb, + const void *daddr) +{ + return __neigh_lookup_errno(&dn_neigh_table, daddr, dst->dev); +} + +static int dn_rt_set_next_hop(struct dn_route *rt, struct dn_fib_res *res) +{ + struct dn_fib_info *fi = res->fi; + struct net_device *dev = rt->dst.dev; + unsigned int mss_metric; + struct neighbour *n; + + if (fi) { + if (DN_FIB_RES_GW(*res) && + DN_FIB_RES_NH(*res).nh_scope == RT_SCOPE_LINK) + rt->rt_gateway = DN_FIB_RES_GW(*res); + dst_init_metrics(&rt->dst, fi->fib_metrics, true); + } + rt->rt_type = res->type; + + if (dev != NULL && rt->n == NULL) { + n = __neigh_lookup_errno(&dn_neigh_table, &rt->rt_gateway, dev); + if (IS_ERR(n)) + return PTR_ERR(n); + rt->n = n; + } + + if (dst_metric(&rt->dst, RTAX_MTU) > rt->dst.dev->mtu) + dst_metric_set(&rt->dst, RTAX_MTU, rt->dst.dev->mtu); + mss_metric = dst_metric_raw(&rt->dst, RTAX_ADVMSS); + if (mss_metric) { + unsigned int mss = dn_mss_from_pmtu(dev, dst_mtu(&rt->dst)); + if (mss_metric > mss) + dst_metric_set(&rt->dst, RTAX_ADVMSS, mss); + } + return 0; +} + +static inline int dn_match_addr(__le16 addr1, __le16 addr2) +{ + __u16 tmp = le16_to_cpu(addr1) ^ le16_to_cpu(addr2); + int match = 16; + while(tmp) { + tmp >>= 1; + match--; + } + return match; +} + +static __le16 dnet_select_source(const struct net_device *dev, __le16 daddr, int scope) +{ + __le16 saddr = 0; + struct dn_dev *dn_db; + struct dn_ifaddr *ifa; + int best_match = 0; + int ret; + + rcu_read_lock(); + dn_db = rcu_dereference(dev->dn_ptr); + for (ifa = rcu_dereference(dn_db->ifa_list); + ifa != NULL; + ifa = rcu_dereference(ifa->ifa_next)) { + if (ifa->ifa_scope > scope) + continue; + if (!daddr) { + saddr = ifa->ifa_local; + break; + } + ret = dn_match_addr(daddr, ifa->ifa_local); + if (ret > best_match) + saddr = ifa->ifa_local; + if (best_match == 0) + saddr = ifa->ifa_local; + } + rcu_read_unlock(); + + return saddr; +} + +static inline __le16 __dn_fib_res_prefsrc(struct dn_fib_res *res) +{ + return dnet_select_source(DN_FIB_RES_DEV(*res), DN_FIB_RES_GW(*res), res->scope); +} + +static inline __le16 dn_fib_rules_map_destination(__le16 daddr, struct dn_fib_res *res) +{ + __le16 mask = dnet_make_mask(res->prefixlen); + return (daddr&~mask)|res->fi->fib_nh->nh_gw; +} + +static int dn_route_output_slow(struct dst_entry **pprt, const struct flowidn *oldflp, int try_hard) +{ + struct flowidn fld = { + .daddr = oldflp->daddr, + .saddr = oldflp->saddr, + .flowidn_scope = RT_SCOPE_UNIVERSE, + .flowidn_mark = oldflp->flowidn_mark, + .flowidn_iif = LOOPBACK_IFINDEX, + .flowidn_oif = oldflp->flowidn_oif, + }; + struct dn_route *rt = NULL; + struct net_device *dev_out = NULL, *dev; + struct neighbour *neigh = NULL; + unsigned int hash; + unsigned int flags = 0; + struct dn_fib_res res = { .fi = NULL, .type = RTN_UNICAST }; + int err; + int free_res = 0; + __le16 gateway = 0; + + if (decnet_debug_level & 16) + printk(KERN_DEBUG + "dn_route_output_slow: dst=%04x src=%04x mark=%d" + " iif=%d oif=%d\n", le16_to_cpu(oldflp->daddr), + le16_to_cpu(oldflp->saddr), + oldflp->flowidn_mark, LOOPBACK_IFINDEX, + oldflp->flowidn_oif); + + /* If we have an output interface, verify its a DECnet device */ + if (oldflp->flowidn_oif) { + dev_out = dev_get_by_index(&init_net, oldflp->flowidn_oif); + err = -ENODEV; + if (dev_out && dev_out->dn_ptr == NULL) { + dev_put(dev_out); + dev_out = NULL; + } + if (dev_out == NULL) + goto out; + } + + /* If we have a source address, verify that its a local address */ + if (oldflp->saddr) { + err = -EADDRNOTAVAIL; + + if (dev_out) { + if (dn_dev_islocal(dev_out, oldflp->saddr)) + goto source_ok; + dev_put(dev_out); + goto out; + } + rcu_read_lock(); + for_each_netdev_rcu(&init_net, dev) { + if (!dev->dn_ptr) + continue; + if (!dn_dev_islocal(dev, oldflp->saddr)) + continue; + if ((dev->flags & IFF_LOOPBACK) && + oldflp->daddr && + !dn_dev_islocal(dev, oldflp->daddr)) + continue; + + dev_out = dev; + break; + } + rcu_read_unlock(); + if (dev_out == NULL) + goto out; + dev_hold(dev_out); +source_ok: + ; + } + + /* No destination? Assume its local */ + if (!fld.daddr) { + fld.daddr = fld.saddr; + + err = -EADDRNOTAVAIL; + if (dev_out) + dev_put(dev_out); + dev_out = init_net.loopback_dev; + dev_hold(dev_out); + if (!fld.daddr) { + fld.daddr = + fld.saddr = dnet_select_source(dev_out, 0, + RT_SCOPE_HOST); + if (!fld.daddr) + goto out; + } + fld.flowidn_oif = LOOPBACK_IFINDEX; + res.type = RTN_LOCAL; + goto make_route; + } + + if (decnet_debug_level & 16) + printk(KERN_DEBUG + "dn_route_output_slow: initial checks complete." + " dst=%04x src=%04x oif=%d try_hard=%d\n", + le16_to_cpu(fld.daddr), le16_to_cpu(fld.saddr), + fld.flowidn_oif, try_hard); + + /* + * N.B. If the kernel is compiled without router support then + * dn_fib_lookup() will evaluate to non-zero so this if () block + * will always be executed. + */ + err = -ESRCH; + if (try_hard || (err = dn_fib_lookup(&fld, &res)) != 0) { + struct dn_dev *dn_db; + if (err != -ESRCH) + goto out; + /* + * Here the fallback is basically the standard algorithm for + * routing in endnodes which is described in the DECnet routing + * docs + * + * If we are not trying hard, look in neighbour cache. + * The result is tested to ensure that if a specific output + * device/source address was requested, then we honour that + * here + */ + if (!try_hard) { + neigh = neigh_lookup_nodev(&dn_neigh_table, &init_net, &fld.daddr); + if (neigh) { + if ((oldflp->flowidn_oif && + (neigh->dev->ifindex != oldflp->flowidn_oif)) || + (oldflp->saddr && + (!dn_dev_islocal(neigh->dev, + oldflp->saddr)))) { + neigh_release(neigh); + neigh = NULL; + } else { + if (dev_out) + dev_put(dev_out); + if (dn_dev_islocal(neigh->dev, fld.daddr)) { + dev_out = init_net.loopback_dev; + res.type = RTN_LOCAL; + } else { + dev_out = neigh->dev; + } + dev_hold(dev_out); + goto select_source; + } + } + } + + /* Not there? Perhaps its a local address */ + if (dev_out == NULL) + dev_out = dn_dev_get_default(); + err = -ENODEV; + if (dev_out == NULL) + goto out; + dn_db = rcu_dereference_raw(dev_out->dn_ptr); + /* Possible improvement - check all devices for local addr */ + if (dn_dev_islocal(dev_out, fld.daddr)) { + dev_put(dev_out); + dev_out = init_net.loopback_dev; + dev_hold(dev_out); + res.type = RTN_LOCAL; + goto select_source; + } + /* Not local either.... try sending it to the default router */ + neigh = neigh_clone(dn_db->router); + BUG_ON(neigh && neigh->dev != dev_out); + + /* Ok then, we assume its directly connected and move on */ +select_source: + if (neigh) + gateway = ((struct dn_neigh *)neigh)->addr; + if (gateway == 0) + gateway = fld.daddr; + if (fld.saddr == 0) { + fld.saddr = dnet_select_source(dev_out, gateway, + res.type == RTN_LOCAL ? + RT_SCOPE_HOST : + RT_SCOPE_LINK); + if (fld.saddr == 0 && res.type != RTN_LOCAL) + goto e_addr; + } + fld.flowidn_oif = dev_out->ifindex; + goto make_route; + } + free_res = 1; + + if (res.type == RTN_NAT) + goto e_inval; + + if (res.type == RTN_LOCAL) { + if (!fld.saddr) + fld.saddr = fld.daddr; + if (dev_out) + dev_put(dev_out); + dev_out = init_net.loopback_dev; + dev_hold(dev_out); + fld.flowidn_oif = dev_out->ifindex; + if (res.fi) + dn_fib_info_put(res.fi); + res.fi = NULL; + goto make_route; + } + + if (res.fi->fib_nhs > 1 && fld.flowidn_oif == 0) + dn_fib_select_multipath(&fld, &res); + + /* + * We could add some logic to deal with default routes here and + * get rid of some of the special casing above. + */ + + if (!fld.saddr) + fld.saddr = DN_FIB_RES_PREFSRC(res); + + if (dev_out) + dev_put(dev_out); + dev_out = DN_FIB_RES_DEV(res); + dev_hold(dev_out); + fld.flowidn_oif = dev_out->ifindex; + gateway = DN_FIB_RES_GW(res); + +make_route: + if (dev_out->flags & IFF_LOOPBACK) + flags |= RTCF_LOCAL; + + rt = dst_alloc(&dn_dst_ops, dev_out, 1, DST_OBSOLETE_NONE, DST_HOST); + if (rt == NULL) + goto e_nobufs; + + memset(&rt->fld, 0, sizeof(rt->fld)); + rt->fld.saddr = oldflp->saddr; + rt->fld.daddr = oldflp->daddr; + rt->fld.flowidn_oif = oldflp->flowidn_oif; + rt->fld.flowidn_iif = 0; + rt->fld.flowidn_mark = oldflp->flowidn_mark; + + rt->rt_saddr = fld.saddr; + rt->rt_daddr = fld.daddr; + rt->rt_gateway = gateway ? gateway : fld.daddr; + rt->rt_local_src = fld.saddr; + + rt->rt_dst_map = fld.daddr; + rt->rt_src_map = fld.saddr; + + rt->n = neigh; + neigh = NULL; + + rt->dst.lastuse = jiffies; + rt->dst.output = dn_output; + rt->dst.input = dn_rt_bug; + rt->rt_flags = flags; + if (flags & RTCF_LOCAL) + rt->dst.input = dn_nsp_rx; + + err = dn_rt_set_next_hop(rt, &res); + if (err) + goto e_neighbour; + + hash = dn_hash(rt->fld.saddr, rt->fld.daddr); + dn_insert_route(rt, hash, (struct dn_route **)pprt); + +done: + if (neigh) + neigh_release(neigh); + if (free_res) + dn_fib_res_put(&res); + if (dev_out) + dev_put(dev_out); +out: + return err; + +e_addr: + err = -EADDRNOTAVAIL; + goto done; +e_inval: + err = -EINVAL; + goto done; +e_nobufs: + err = -ENOBUFS; + goto done; +e_neighbour: + dst_free(&rt->dst); + goto e_nobufs; +} + + +/* + * N.B. The flags may be moved into the flowi at some future stage. + */ +static int __dn_route_output_key(struct dst_entry **pprt, const struct flowidn *flp, int flags) +{ + unsigned int hash = dn_hash(flp->saddr, flp->daddr); + struct dn_route *rt = NULL; + + if (!(flags & MSG_TRYHARD)) { + rcu_read_lock_bh(); + for (rt = rcu_dereference_bh(dn_rt_hash_table[hash].chain); rt; + rt = rcu_dereference_bh(rt->dst.dn_next)) { + if ((flp->daddr == rt->fld.daddr) && + (flp->saddr == rt->fld.saddr) && + (flp->flowidn_mark == rt->fld.flowidn_mark) && + dn_is_output_route(rt) && + (rt->fld.flowidn_oif == flp->flowidn_oif)) { + dst_use(&rt->dst, jiffies); + rcu_read_unlock_bh(); + *pprt = &rt->dst; + return 0; + } + } + rcu_read_unlock_bh(); + } + + return dn_route_output_slow(pprt, flp, flags); +} + +static int dn_route_output_key(struct dst_entry **pprt, struct flowidn *flp, int flags) +{ + int err; + + err = __dn_route_output_key(pprt, flp, flags); + if (err == 0 && flp->flowidn_proto) { + *pprt = xfrm_lookup(&init_net, *pprt, + flowidn_to_flowi(flp), NULL, 0); + if (IS_ERR(*pprt)) { + err = PTR_ERR(*pprt); + *pprt = NULL; + } + } + return err; +} + +int dn_route_output_sock(struct dst_entry __rcu **pprt, struct flowidn *fl, struct sock *sk, int flags) +{ + int err; + + err = __dn_route_output_key(pprt, fl, flags & MSG_TRYHARD); + if (err == 0 && fl->flowidn_proto) { + *pprt = xfrm_lookup(&init_net, *pprt, + flowidn_to_flowi(fl), sk, 0); + if (IS_ERR(*pprt)) { + err = PTR_ERR(*pprt); + *pprt = NULL; + } + } + return err; +} + +static int dn_route_input_slow(struct sk_buff *skb) +{ + struct dn_route *rt = NULL; + struct dn_skb_cb *cb = DN_SKB_CB(skb); + struct net_device *in_dev = skb->dev; + struct net_device *out_dev = NULL; + struct dn_dev *dn_db; + struct neighbour *neigh = NULL; + unsigned int hash; + int flags = 0; + __le16 gateway = 0; + __le16 local_src = 0; + struct flowidn fld = { + .daddr = cb->dst, + .saddr = cb->src, + .flowidn_scope = RT_SCOPE_UNIVERSE, + .flowidn_mark = skb->mark, + .flowidn_iif = skb->dev->ifindex, + }; + struct dn_fib_res res = { .fi = NULL, .type = RTN_UNREACHABLE }; + int err = -EINVAL; + int free_res = 0; + + dev_hold(in_dev); + + if ((dn_db = rcu_dereference(in_dev->dn_ptr)) == NULL) + goto out; + + /* Zero source addresses are not allowed */ + if (fld.saddr == 0) + goto out; + + /* + * In this case we've just received a packet from a source + * outside ourselves pretending to come from us. We don't + * allow it any further to prevent routing loops, spoofing and + * other nasties. Loopback packets already have the dst attached + * so this only affects packets which have originated elsewhere. + */ + err = -ENOTUNIQ; + if (dn_dev_islocal(in_dev, cb->src)) + goto out; + + err = dn_fib_lookup(&fld, &res); + if (err) { + if (err != -ESRCH) + goto out; + /* + * Is the destination us ? + */ + if (!dn_dev_islocal(in_dev, cb->dst)) + goto e_inval; + + res.type = RTN_LOCAL; + } else { + __le16 src_map = fld.saddr; + free_res = 1; + + out_dev = DN_FIB_RES_DEV(res); + if (out_dev == NULL) { + net_crit_ratelimited("Bug in dn_route_input_slow() No output device\n"); + goto e_inval; + } + dev_hold(out_dev); + + if (res.r) + src_map = fld.saddr; /* no NAT support for now */ + + gateway = DN_FIB_RES_GW(res); + if (res.type == RTN_NAT) { + fld.daddr = dn_fib_rules_map_destination(fld.daddr, &res); + dn_fib_res_put(&res); + free_res = 0; + if (dn_fib_lookup(&fld, &res)) + goto e_inval; + free_res = 1; + if (res.type != RTN_UNICAST) + goto e_inval; + flags |= RTCF_DNAT; + gateway = fld.daddr; + } + fld.saddr = src_map; + } + + switch(res.type) { + case RTN_UNICAST: + /* + * Forwarding check here, we only check for forwarding + * being turned off, if you want to only forward intra + * area, its up to you to set the routing tables up + * correctly. + */ + if (dn_db->parms.forwarding == 0) + goto e_inval; + + if (res.fi->fib_nhs > 1 && fld.flowidn_oif == 0) + dn_fib_select_multipath(&fld, &res); + + /* + * Check for out_dev == in_dev. We use the RTCF_DOREDIRECT + * flag as a hint to set the intra-ethernet bit when + * forwarding. If we've got NAT in operation, we don't do + * this optimisation. + */ + if (out_dev == in_dev && !(flags & RTCF_NAT)) + flags |= RTCF_DOREDIRECT; + + local_src = DN_FIB_RES_PREFSRC(res); + + case RTN_BLACKHOLE: + case RTN_UNREACHABLE: + break; + case RTN_LOCAL: + flags |= RTCF_LOCAL; + fld.saddr = cb->dst; + fld.daddr = cb->src; + + /* Routing tables gave us a gateway */ + if (gateway) + goto make_route; + + /* Packet was intra-ethernet, so we know its on-link */ + if (cb->rt_flags & DN_RT_F_IE) { + gateway = cb->src; + goto make_route; + } + + /* Use the default router if there is one */ + neigh = neigh_clone(dn_db->router); + if (neigh) { + gateway = ((struct dn_neigh *)neigh)->addr; + goto make_route; + } + + /* Close eyes and pray */ + gateway = cb->src; + goto make_route; + default: + goto e_inval; + } + +make_route: + rt = dst_alloc(&dn_dst_ops, out_dev, 0, DST_OBSOLETE_NONE, DST_HOST); + if (rt == NULL) + goto e_nobufs; + + memset(&rt->fld, 0, sizeof(rt->fld)); + rt->rt_saddr = fld.saddr; + rt->rt_daddr = fld.daddr; + rt->rt_gateway = fld.daddr; + if (gateway) + rt->rt_gateway = gateway; + rt->rt_local_src = local_src ? local_src : rt->rt_saddr; + + rt->rt_dst_map = fld.daddr; + rt->rt_src_map = fld.saddr; + + rt->fld.saddr = cb->src; + rt->fld.daddr = cb->dst; + rt->fld.flowidn_oif = 0; + rt->fld.flowidn_iif = in_dev->ifindex; + rt->fld.flowidn_mark = fld.flowidn_mark; + + rt->n = neigh; + rt->dst.lastuse = jiffies; + rt->dst.output = dn_rt_bug_sk; + switch (res.type) { + case RTN_UNICAST: + rt->dst.input = dn_forward; + break; + case RTN_LOCAL: + rt->dst.output = dn_output; + rt->dst.input = dn_nsp_rx; + rt->dst.dev = in_dev; + flags |= RTCF_LOCAL; + break; + default: + case RTN_UNREACHABLE: + case RTN_BLACKHOLE: + rt->dst.input = dst_discard; + } + rt->rt_flags = flags; + + err = dn_rt_set_next_hop(rt, &res); + if (err) + goto e_neighbour; + + hash = dn_hash(rt->fld.saddr, rt->fld.daddr); + dn_insert_route(rt, hash, &rt); + skb_dst_set(skb, &rt->dst); + +done: + if (neigh) + neigh_release(neigh); + if (free_res) + dn_fib_res_put(&res); + dev_put(in_dev); + if (out_dev) + dev_put(out_dev); +out: + return err; + +e_inval: + err = -EINVAL; + goto done; + +e_nobufs: + err = -ENOBUFS; + goto done; + +e_neighbour: + dst_free(&rt->dst); + goto done; +} + +static int dn_route_input(struct sk_buff *skb) +{ + struct dn_route *rt; + struct dn_skb_cb *cb = DN_SKB_CB(skb); + unsigned int hash = dn_hash(cb->src, cb->dst); + + if (skb_dst(skb)) + return 0; + + rcu_read_lock(); + for(rt = rcu_dereference(dn_rt_hash_table[hash].chain); rt != NULL; + rt = rcu_dereference(rt->dst.dn_next)) { + if ((rt->fld.saddr == cb->src) && + (rt->fld.daddr == cb->dst) && + (rt->fld.flowidn_oif == 0) && + (rt->fld.flowidn_mark == skb->mark) && + (rt->fld.flowidn_iif == cb->iif)) { + dst_use(&rt->dst, jiffies); + rcu_read_unlock(); + skb_dst_set(skb, (struct dst_entry *)rt); + return 0; + } + } + rcu_read_unlock(); + + return dn_route_input_slow(skb); +} + +static int dn_rt_fill_info(struct sk_buff *skb, u32 portid, u32 seq, + int event, int nowait, unsigned int flags) +{ + struct dn_route *rt = (struct dn_route *)skb_dst(skb); + struct rtmsg *r; + struct nlmsghdr *nlh; + long expires; + + nlh = nlmsg_put(skb, portid, seq, event, sizeof(*r), flags); + if (!nlh) + return -EMSGSIZE; + + r = nlmsg_data(nlh); + r->rtm_family = AF_DECnet; + r->rtm_dst_len = 16; + r->rtm_src_len = 0; + r->rtm_tos = 0; + r->rtm_table = RT_TABLE_MAIN; + r->rtm_type = rt->rt_type; + r->rtm_flags = (rt->rt_flags & ~0xFFFF) | RTM_F_CLONED; + r->rtm_scope = RT_SCOPE_UNIVERSE; + r->rtm_protocol = RTPROT_UNSPEC; + + if (rt->rt_flags & RTCF_NOTIFY) + r->rtm_flags |= RTM_F_NOTIFY; + + if (nla_put_u32(skb, RTA_TABLE, RT_TABLE_MAIN) < 0 || + nla_put_le16(skb, RTA_DST, rt->rt_daddr) < 0) + goto errout; + + if (rt->fld.saddr) { + r->rtm_src_len = 16; + if (nla_put_le16(skb, RTA_SRC, rt->fld.saddr) < 0) + goto errout; + } + if (rt->dst.dev && + nla_put_u32(skb, RTA_OIF, rt->dst.dev->ifindex) < 0) + goto errout; + + /* + * Note to self - change this if input routes reverse direction when + * they deal only with inputs and not with replies like they do + * currently. + */ + if (nla_put_le16(skb, RTA_PREFSRC, rt->rt_local_src) < 0) + goto errout; + + if (rt->rt_daddr != rt->rt_gateway && + nla_put_le16(skb, RTA_GATEWAY, rt->rt_gateway) < 0) + goto errout; + + if (rtnetlink_put_metrics(skb, dst_metrics_ptr(&rt->dst)) < 0) + goto errout; + + expires = rt->dst.expires ? rt->dst.expires - jiffies : 0; + if (rtnl_put_cacheinfo(skb, &rt->dst, 0, expires, + rt->dst.error) < 0) + goto errout; + + if (dn_is_input_route(rt) && + nla_put_u32(skb, RTA_IIF, rt->fld.flowidn_iif) < 0) + goto errout; + + nlmsg_end(skb, nlh); + return 0; + +errout: + nlmsg_cancel(skb, nlh); + return -EMSGSIZE; +} + +const struct nla_policy rtm_dn_policy[RTA_MAX + 1] = { + [RTA_DST] = { .type = NLA_U16 }, + [RTA_SRC] = { .type = NLA_U16 }, + [RTA_IIF] = { .type = NLA_U32 }, + [RTA_OIF] = { .type = NLA_U32 }, + [RTA_GATEWAY] = { .type = NLA_U16 }, + [RTA_PRIORITY] = { .type = NLA_U32 }, + [RTA_PREFSRC] = { .type = NLA_U16 }, + [RTA_METRICS] = { .type = NLA_NESTED }, + [RTA_MULTIPATH] = { .type = NLA_NESTED }, + [RTA_TABLE] = { .type = NLA_U32 }, + [RTA_MARK] = { .type = NLA_U32 }, +}; + +/* + * This is called by both endnodes and routers now. + */ +static int dn_cache_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh) +{ + struct net *net = sock_net(in_skb->sk); + struct rtmsg *rtm = nlmsg_data(nlh); + struct dn_route *rt = NULL; + struct dn_skb_cb *cb; + int err; + struct sk_buff *skb; + struct flowidn fld; + struct nlattr *tb[RTA_MAX+1]; + + if (!net_eq(net, &init_net)) + return -EINVAL; + + err = nlmsg_parse(nlh, sizeof(*rtm), tb, RTA_MAX, rtm_dn_policy); + if (err < 0) + return err; + + memset(&fld, 0, sizeof(fld)); + fld.flowidn_proto = DNPROTO_NSP; + + skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (skb == NULL) + return -ENOBUFS; + skb_reset_mac_header(skb); + cb = DN_SKB_CB(skb); + + if (tb[RTA_SRC]) + fld.saddr = nla_get_le16(tb[RTA_SRC]); + + if (tb[RTA_DST]) + fld.daddr = nla_get_le16(tb[RTA_DST]); + + if (tb[RTA_IIF]) + fld.flowidn_iif = nla_get_u32(tb[RTA_IIF]); + + if (fld.flowidn_iif) { + struct net_device *dev; + dev = __dev_get_by_index(&init_net, fld.flowidn_iif); + if (!dev || !dev->dn_ptr) { + kfree_skb(skb); + return -ENODEV; + } + skb->protocol = htons(ETH_P_DNA_RT); + skb->dev = dev; + cb->src = fld.saddr; + cb->dst = fld.daddr; + local_bh_disable(); + err = dn_route_input(skb); + local_bh_enable(); + memset(cb, 0, sizeof(struct dn_skb_cb)); + rt = (struct dn_route *)skb_dst(skb); + if (!err && -rt->dst.error) + err = rt->dst.error; + } else { + if (tb[RTA_OIF]) + fld.flowidn_oif = nla_get_u32(tb[RTA_OIF]); + + err = dn_route_output_key((struct dst_entry **)&rt, &fld, 0); + } + + skb->dev = NULL; + if (err) + goto out_free; + skb_dst_set(skb, &rt->dst); + if (rtm->rtm_flags & RTM_F_NOTIFY) + rt->rt_flags |= RTCF_NOTIFY; + + err = dn_rt_fill_info(skb, NETLINK_CB(in_skb).portid, nlh->nlmsg_seq, RTM_NEWROUTE, 0, 0); + if (err < 0) { + err = -EMSGSIZE; + goto out_free; + } + + return rtnl_unicast(skb, &init_net, NETLINK_CB(in_skb).portid); + +out_free: + kfree_skb(skb); + return err; +} + +/* + * For routers, this is called from dn_fib_dump, but for endnodes its + * called directly from the rtnetlink dispatch table. + */ +int dn_cache_dump(struct sk_buff *skb, struct netlink_callback *cb) +{ + struct net *net = sock_net(skb->sk); + struct dn_route *rt; + int h, s_h; + int idx, s_idx; + struct rtmsg *rtm; + + if (!net_eq(net, &init_net)) + return 0; + + if (nlmsg_len(cb->nlh) < sizeof(struct rtmsg)) + return -EINVAL; + + rtm = nlmsg_data(cb->nlh); + if (!(rtm->rtm_flags & RTM_F_CLONED)) + return 0; + + s_h = cb->args[0]; + s_idx = idx = cb->args[1]; + for(h = 0; h <= dn_rt_hash_mask; h++) { + if (h < s_h) + continue; + if (h > s_h) + s_idx = 0; + rcu_read_lock_bh(); + for(rt = rcu_dereference_bh(dn_rt_hash_table[h].chain), idx = 0; + rt; + rt = rcu_dereference_bh(rt->dst.dn_next), idx++) { + if (idx < s_idx) + continue; + skb_dst_set(skb, dst_clone(&rt->dst)); + if (dn_rt_fill_info(skb, NETLINK_CB(cb->skb).portid, + cb->nlh->nlmsg_seq, RTM_NEWROUTE, + 1, NLM_F_MULTI) < 0) { + skb_dst_drop(skb); + rcu_read_unlock_bh(); + goto done; + } + skb_dst_drop(skb); + } + rcu_read_unlock_bh(); + } + +done: + cb->args[0] = h; + cb->args[1] = idx; + return skb->len; +} + +#ifdef CONFIG_PROC_FS +struct dn_rt_cache_iter_state { + int bucket; +}; + +static struct dn_route *dn_rt_cache_get_first(struct seq_file *seq) +{ + struct dn_route *rt = NULL; + struct dn_rt_cache_iter_state *s = seq->private; + + for(s->bucket = dn_rt_hash_mask; s->bucket >= 0; --s->bucket) { + rcu_read_lock_bh(); + rt = rcu_dereference_bh(dn_rt_hash_table[s->bucket].chain); + if (rt) + break; + rcu_read_unlock_bh(); + } + return rt; +} + +static struct dn_route *dn_rt_cache_get_next(struct seq_file *seq, struct dn_route *rt) +{ + struct dn_rt_cache_iter_state *s = seq->private; + + rt = rcu_dereference_bh(rt->dst.dn_next); + while (!rt) { + rcu_read_unlock_bh(); + if (--s->bucket < 0) + break; + rcu_read_lock_bh(); + rt = rcu_dereference_bh(dn_rt_hash_table[s->bucket].chain); + } + return rt; +} + +static void *dn_rt_cache_seq_start(struct seq_file *seq, loff_t *pos) +{ + struct dn_route *rt = dn_rt_cache_get_first(seq); + + if (rt) { + while(*pos && (rt = dn_rt_cache_get_next(seq, rt))) + --*pos; + } + return *pos ? NULL : rt; +} + +static void *dn_rt_cache_seq_next(struct seq_file *seq, void *v, loff_t *pos) +{ + struct dn_route *rt = dn_rt_cache_get_next(seq, v); + ++*pos; + return rt; +} + +static void dn_rt_cache_seq_stop(struct seq_file *seq, void *v) +{ + if (v) + rcu_read_unlock_bh(); +} + +static int dn_rt_cache_seq_show(struct seq_file *seq, void *v) +{ + struct dn_route *rt = v; + char buf1[DN_ASCBUF_LEN], buf2[DN_ASCBUF_LEN]; + + seq_printf(seq, "%-8s %-7s %-7s %04d %04d %04d\n", + rt->dst.dev ? rt->dst.dev->name : "*", + dn_addr2asc(le16_to_cpu(rt->rt_daddr), buf1), + dn_addr2asc(le16_to_cpu(rt->rt_saddr), buf2), + atomic_read(&rt->dst.__refcnt), + rt->dst.__use, 0); + return 0; +} + +static const struct seq_operations dn_rt_cache_seq_ops = { + .start = dn_rt_cache_seq_start, + .next = dn_rt_cache_seq_next, + .stop = dn_rt_cache_seq_stop, + .show = dn_rt_cache_seq_show, +}; + +static int dn_rt_cache_seq_open(struct inode *inode, struct file *file) +{ + return seq_open_private(file, &dn_rt_cache_seq_ops, + sizeof(struct dn_rt_cache_iter_state)); +} + +static const struct file_operations dn_rt_cache_seq_fops = { + .owner = THIS_MODULE, + .open = dn_rt_cache_seq_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release_private, +}; + +#endif /* CONFIG_PROC_FS */ + +void __init dn_route_init(void) +{ + int i, goal, order; + + dn_dst_ops.kmem_cachep = + kmem_cache_create("dn_dst_cache", sizeof(struct dn_route), 0, + SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL); + dst_entries_init(&dn_dst_ops); + setup_timer(&dn_route_timer, dn_dst_check_expire, 0); + dn_route_timer.expires = jiffies + decnet_dst_gc_interval * HZ; + add_timer(&dn_route_timer); + + goal = totalram_pages >> (26 - PAGE_SHIFT); + + for(order = 0; (1UL << order) < goal; order++) + /* NOTHING */; + + /* + * Only want 1024 entries max, since the table is very, very unlikely + * to be larger than that. + */ + while(order && ((((1UL << order) * PAGE_SIZE) / + sizeof(struct dn_rt_hash_bucket)) >= 2048)) + order--; + + do { + dn_rt_hash_mask = (1UL << order) * PAGE_SIZE / + sizeof(struct dn_rt_hash_bucket); + while(dn_rt_hash_mask & (dn_rt_hash_mask - 1)) + dn_rt_hash_mask--; + dn_rt_hash_table = (struct dn_rt_hash_bucket *) + __get_free_pages(GFP_ATOMIC, order); + } while (dn_rt_hash_table == NULL && --order > 0); + + if (!dn_rt_hash_table) + panic("Failed to allocate DECnet route cache hash table\n"); + + printk(KERN_INFO + "DECnet: Routing cache hash table of %u buckets, %ldKbytes\n", + dn_rt_hash_mask, + (long)(dn_rt_hash_mask*sizeof(struct dn_rt_hash_bucket))/1024); + + dn_rt_hash_mask--; + for(i = 0; i <= dn_rt_hash_mask; i++) { + spin_lock_init(&dn_rt_hash_table[i].lock); + dn_rt_hash_table[i].chain = NULL; + } + + dn_dst_ops.gc_thresh = (dn_rt_hash_mask + 1); + + proc_create("decnet_cache", S_IRUGO, init_net.proc_net, + &dn_rt_cache_seq_fops); + +#ifdef CONFIG_DECNET_ROUTER + rtnl_register(PF_DECnet, RTM_GETROUTE, dn_cache_getroute, + dn_fib_dump, NULL); +#else + rtnl_register(PF_DECnet, RTM_GETROUTE, dn_cache_getroute, + dn_cache_dump, NULL); +#endif +} + +void __exit dn_route_cleanup(void) +{ + del_timer(&dn_route_timer); + dn_run_flush(0); + + remove_proc_entry("decnet_cache", init_net.proc_net); + dst_entries_destroy(&dn_dst_ops); +} + diff --git a/net/decnet/dn_rules.c b/net/decnet/dn_rules.c new file mode 100644 index 000000000..9d66a0f72 --- /dev/null +++ b/net/decnet/dn_rules.c @@ -0,0 +1,257 @@ + +/* + * DECnet An implementation of the DECnet protocol suite for the LINUX + * operating system. DECnet is implemented using the BSD Socket + * interface as the means of communication with the user level. + * + * DECnet Routing Forwarding Information Base (Rules) + * + * Author: Steve Whitehouse <SteveW@ACM.org> + * Mostly copied from Alexey Kuznetsov's ipv4/fib_rules.c + * + * + * Changes: + * Steve Whitehouse <steve@chygwyn.com> + * Updated for Thomas Graf's generic rules + * + */ +#include <linux/net.h> +#include <linux/init.h> +#include <linux/netlink.h> +#include <linux/rtnetlink.h> +#include <linux/netdevice.h> +#include <linux/spinlock.h> +#include <linux/list.h> +#include <linux/rcupdate.h> +#include <linux/export.h> +#include <net/neighbour.h> +#include <net/dst.h> +#include <net/flow.h> +#include <net/fib_rules.h> +#include <net/dn.h> +#include <net/dn_fib.h> +#include <net/dn_neigh.h> +#include <net/dn_dev.h> +#include <net/dn_route.h> + +static struct fib_rules_ops *dn_fib_rules_ops; + +struct dn_fib_rule +{ + struct fib_rule common; + unsigned char dst_len; + unsigned char src_len; + __le16 src; + __le16 srcmask; + __le16 dst; + __le16 dstmask; + __le16 srcmap; + u8 flags; +}; + + +int dn_fib_lookup(struct flowidn *flp, struct dn_fib_res *res) +{ + struct fib_lookup_arg arg = { + .result = res, + }; + int err; + + err = fib_rules_lookup(dn_fib_rules_ops, + flowidn_to_flowi(flp), 0, &arg); + res->r = arg.rule; + + return err; +} + +static int dn_fib_rule_action(struct fib_rule *rule, struct flowi *flp, + int flags, struct fib_lookup_arg *arg) +{ + struct flowidn *fld = &flp->u.dn; + int err = -EAGAIN; + struct dn_fib_table *tbl; + + switch(rule->action) { + case FR_ACT_TO_TBL: + break; + + case FR_ACT_UNREACHABLE: + err = -ENETUNREACH; + goto errout; + + case FR_ACT_PROHIBIT: + err = -EACCES; + goto errout; + + case FR_ACT_BLACKHOLE: + default: + err = -EINVAL; + goto errout; + } + + tbl = dn_fib_get_table(rule->table, 0); + if (tbl == NULL) + goto errout; + + err = tbl->lookup(tbl, fld, (struct dn_fib_res *)arg->result); + if (err > 0) + err = -EAGAIN; +errout: + return err; +} + +static const struct nla_policy dn_fib_rule_policy[FRA_MAX+1] = { + FRA_GENERIC_POLICY, +}; + +static int dn_fib_rule_match(struct fib_rule *rule, struct flowi *fl, int flags) +{ + struct dn_fib_rule *r = (struct dn_fib_rule *)rule; + struct flowidn *fld = &fl->u.dn; + __le16 daddr = fld->daddr; + __le16 saddr = fld->saddr; + + if (((saddr ^ r->src) & r->srcmask) || + ((daddr ^ r->dst) & r->dstmask)) + return 0; + + return 1; +} + +static int dn_fib_rule_configure(struct fib_rule *rule, struct sk_buff *skb, + struct fib_rule_hdr *frh, + struct nlattr **tb) +{ + int err = -EINVAL; + struct dn_fib_rule *r = (struct dn_fib_rule *)rule; + + if (frh->tos) + goto errout; + + if (rule->table == RT_TABLE_UNSPEC) { + if (rule->action == FR_ACT_TO_TBL) { + struct dn_fib_table *table; + + table = dn_fib_empty_table(); + if (table == NULL) { + err = -ENOBUFS; + goto errout; + } + + rule->table = table->n; + } + } + + if (frh->src_len) + r->src = nla_get_le16(tb[FRA_SRC]); + + if (frh->dst_len) + r->dst = nla_get_le16(tb[FRA_DST]); + + r->src_len = frh->src_len; + r->srcmask = dnet_make_mask(r->src_len); + r->dst_len = frh->dst_len; + r->dstmask = dnet_make_mask(r->dst_len); + err = 0; +errout: + return err; +} + +static int dn_fib_rule_compare(struct fib_rule *rule, struct fib_rule_hdr *frh, + struct nlattr **tb) +{ + struct dn_fib_rule *r = (struct dn_fib_rule *)rule; + + if (frh->src_len && (r->src_len != frh->src_len)) + return 0; + + if (frh->dst_len && (r->dst_len != frh->dst_len)) + return 0; + + if (frh->src_len && (r->src != nla_get_le16(tb[FRA_SRC]))) + return 0; + + if (frh->dst_len && (r->dst != nla_get_le16(tb[FRA_DST]))) + return 0; + + return 1; +} + +unsigned int dnet_addr_type(__le16 addr) +{ + struct flowidn fld = { .daddr = addr }; + struct dn_fib_res res; + unsigned int ret = RTN_UNICAST; + struct dn_fib_table *tb = dn_fib_get_table(RT_TABLE_LOCAL, 0); + + res.r = NULL; + + if (tb) { + if (!tb->lookup(tb, &fld, &res)) { + ret = res.type; + dn_fib_res_put(&res); + } + } + return ret; +} + +static int dn_fib_rule_fill(struct fib_rule *rule, struct sk_buff *skb, + struct fib_rule_hdr *frh) +{ + struct dn_fib_rule *r = (struct dn_fib_rule *)rule; + + frh->dst_len = r->dst_len; + frh->src_len = r->src_len; + frh->tos = 0; + + if ((r->dst_len && + nla_put_le16(skb, FRA_DST, r->dst)) || + (r->src_len && + nla_put_le16(skb, FRA_SRC, r->src))) + goto nla_put_failure; + return 0; + +nla_put_failure: + return -ENOBUFS; +} + +static void dn_fib_rule_flush_cache(struct fib_rules_ops *ops) +{ + dn_rt_cache_flush(-1); +} + +static const struct fib_rules_ops __net_initconst dn_fib_rules_ops_template = { + .family = AF_DECnet, + .rule_size = sizeof(struct dn_fib_rule), + .addr_size = sizeof(u16), + .action = dn_fib_rule_action, + .match = dn_fib_rule_match, + .configure = dn_fib_rule_configure, + .compare = dn_fib_rule_compare, + .fill = dn_fib_rule_fill, + .default_pref = fib_default_rule_pref, + .flush_cache = dn_fib_rule_flush_cache, + .nlgroup = RTNLGRP_DECnet_RULE, + .policy = dn_fib_rule_policy, + .owner = THIS_MODULE, + .fro_net = &init_net, +}; + +void __init dn_fib_rules_init(void) +{ + dn_fib_rules_ops = + fib_rules_register(&dn_fib_rules_ops_template, &init_net); + BUG_ON(IS_ERR(dn_fib_rules_ops)); + BUG_ON(fib_default_rule_add(dn_fib_rules_ops, 0x7fff, + RT_TABLE_MAIN, 0)); +} + +void __exit dn_fib_rules_cleanup(void) +{ + rtnl_lock(); + fib_rules_unregister(dn_fib_rules_ops); + rtnl_unlock(); + rcu_barrier(); +} + + diff --git a/net/decnet/dn_table.c b/net/decnet/dn_table.c new file mode 100644 index 000000000..1540b506e --- /dev/null +++ b/net/decnet/dn_table.c @@ -0,0 +1,926 @@ +/* + * DECnet An implementation of the DECnet protocol suite for the LINUX + * operating system. DECnet is implemented using the BSD Socket + * interface as the means of communication with the user level. + * + * DECnet Routing Forwarding Information Base (Routing Tables) + * + * Author: Steve Whitehouse <SteveW@ACM.org> + * Mostly copied from the IPv4 routing code + * + * + * Changes: + * + */ +#include <linux/string.h> +#include <linux/net.h> +#include <linux/socket.h> +#include <linux/slab.h> +#include <linux/sockios.h> +#include <linux/init.h> +#include <linux/skbuff.h> +#include <linux/rtnetlink.h> +#include <linux/proc_fs.h> +#include <linux/netdevice.h> +#include <linux/timer.h> +#include <linux/spinlock.h> +#include <linux/atomic.h> +#include <asm/uaccess.h> +#include <linux/route.h> /* RTF_xxx */ +#include <net/neighbour.h> +#include <net/netlink.h> +#include <net/tcp.h> +#include <net/dst.h> +#include <net/flow.h> +#include <net/fib_rules.h> +#include <net/dn.h> +#include <net/dn_route.h> +#include <net/dn_fib.h> +#include <net/dn_neigh.h> +#include <net/dn_dev.h> + +struct dn_zone +{ + struct dn_zone *dz_next; + struct dn_fib_node **dz_hash; + int dz_nent; + int dz_divisor; + u32 dz_hashmask; +#define DZ_HASHMASK(dz) ((dz)->dz_hashmask) + int dz_order; + __le16 dz_mask; +#define DZ_MASK(dz) ((dz)->dz_mask) +}; + +struct dn_hash +{ + struct dn_zone *dh_zones[17]; + struct dn_zone *dh_zone_list; +}; + +#define dz_key_0(key) ((key).datum = 0) + +#define for_nexthops(fi) { int nhsel; const struct dn_fib_nh *nh;\ + for(nhsel = 0, nh = (fi)->fib_nh; nhsel < (fi)->fib_nhs; nh++, nhsel++) + +#define endfor_nexthops(fi) } + +#define DN_MAX_DIVISOR 1024 +#define DN_S_ZOMBIE 1 +#define DN_S_ACCESSED 2 + +#define DN_FIB_SCAN(f, fp) \ +for( ; ((f) = *(fp)) != NULL; (fp) = &(f)->fn_next) + +#define DN_FIB_SCAN_KEY(f, fp, key) \ +for( ; ((f) = *(fp)) != NULL && dn_key_eq((f)->fn_key, (key)); (fp) = &(f)->fn_next) + +#define RT_TABLE_MIN 1 +#define DN_FIB_TABLE_HASHSZ 256 +static struct hlist_head dn_fib_table_hash[DN_FIB_TABLE_HASHSZ]; +static DEFINE_RWLOCK(dn_fib_tables_lock); + +static struct kmem_cache *dn_hash_kmem __read_mostly; +static int dn_fib_hash_zombies; + +static inline dn_fib_idx_t dn_hash(dn_fib_key_t key, struct dn_zone *dz) +{ + u16 h = le16_to_cpu(key.datum)>>(16 - dz->dz_order); + h ^= (h >> 10); + h ^= (h >> 6); + h &= DZ_HASHMASK(dz); + return *(dn_fib_idx_t *)&h; +} + +static inline dn_fib_key_t dz_key(__le16 dst, struct dn_zone *dz) +{ + dn_fib_key_t k; + k.datum = dst & DZ_MASK(dz); + return k; +} + +static inline struct dn_fib_node **dn_chain_p(dn_fib_key_t key, struct dn_zone *dz) +{ + return &dz->dz_hash[dn_hash(key, dz).datum]; +} + +static inline struct dn_fib_node *dz_chain(dn_fib_key_t key, struct dn_zone *dz) +{ + return dz->dz_hash[dn_hash(key, dz).datum]; +} + +static inline int dn_key_eq(dn_fib_key_t a, dn_fib_key_t b) +{ + return a.datum == b.datum; +} + +static inline int dn_key_leq(dn_fib_key_t a, dn_fib_key_t b) +{ + return a.datum <= b.datum; +} + +static inline void dn_rebuild_zone(struct dn_zone *dz, + struct dn_fib_node **old_ht, + int old_divisor) +{ + struct dn_fib_node *f, **fp, *next; + int i; + + for(i = 0; i < old_divisor; i++) { + for(f = old_ht[i]; f; f = next) { + next = f->fn_next; + for(fp = dn_chain_p(f->fn_key, dz); + *fp && dn_key_leq((*fp)->fn_key, f->fn_key); + fp = &(*fp)->fn_next) + /* NOTHING */; + f->fn_next = *fp; + *fp = f; + } + } +} + +static void dn_rehash_zone(struct dn_zone *dz) +{ + struct dn_fib_node **ht, **old_ht; + int old_divisor, new_divisor; + u32 new_hashmask; + + old_divisor = dz->dz_divisor; + + switch (old_divisor) { + case 16: + new_divisor = 256; + new_hashmask = 0xFF; + break; + default: + printk(KERN_DEBUG "DECnet: dn_rehash_zone: BUG! %d\n", + old_divisor); + case 256: + new_divisor = 1024; + new_hashmask = 0x3FF; + break; + } + + ht = kcalloc(new_divisor, sizeof(struct dn_fib_node*), GFP_KERNEL); + if (ht == NULL) + return; + + write_lock_bh(&dn_fib_tables_lock); + old_ht = dz->dz_hash; + dz->dz_hash = ht; + dz->dz_hashmask = new_hashmask; + dz->dz_divisor = new_divisor; + dn_rebuild_zone(dz, old_ht, old_divisor); + write_unlock_bh(&dn_fib_tables_lock); + kfree(old_ht); +} + +static void dn_free_node(struct dn_fib_node *f) +{ + dn_fib_release_info(DN_FIB_INFO(f)); + kmem_cache_free(dn_hash_kmem, f); +} + + +static struct dn_zone *dn_new_zone(struct dn_hash *table, int z) +{ + int i; + struct dn_zone *dz = kzalloc(sizeof(struct dn_zone), GFP_KERNEL); + if (!dz) + return NULL; + + if (z) { + dz->dz_divisor = 16; + dz->dz_hashmask = 0x0F; + } else { + dz->dz_divisor = 1; + dz->dz_hashmask = 0; + } + + dz->dz_hash = kcalloc(dz->dz_divisor, sizeof(struct dn_fib_node *), GFP_KERNEL); + if (!dz->dz_hash) { + kfree(dz); + return NULL; + } + + dz->dz_order = z; + dz->dz_mask = dnet_make_mask(z); + + for(i = z + 1; i <= 16; i++) + if (table->dh_zones[i]) + break; + + write_lock_bh(&dn_fib_tables_lock); + if (i>16) { + dz->dz_next = table->dh_zone_list; + table->dh_zone_list = dz; + } else { + dz->dz_next = table->dh_zones[i]->dz_next; + table->dh_zones[i]->dz_next = dz; + } + table->dh_zones[z] = dz; + write_unlock_bh(&dn_fib_tables_lock); + return dz; +} + + +static int dn_fib_nh_match(struct rtmsg *r, struct nlmsghdr *nlh, struct nlattr *attrs[], struct dn_fib_info *fi) +{ + struct rtnexthop *nhp; + int nhlen; + + if (attrs[RTA_PRIORITY] && + nla_get_u32(attrs[RTA_PRIORITY]) != fi->fib_priority) + return 1; + + if (attrs[RTA_OIF] || attrs[RTA_GATEWAY]) { + if ((!attrs[RTA_OIF] || nla_get_u32(attrs[RTA_OIF]) == fi->fib_nh->nh_oif) && + (!attrs[RTA_GATEWAY] || nla_get_le16(attrs[RTA_GATEWAY]) != fi->fib_nh->nh_gw)) + return 0; + return 1; + } + + if (!attrs[RTA_MULTIPATH]) + return 0; + + nhp = nla_data(attrs[RTA_MULTIPATH]); + nhlen = nla_len(attrs[RTA_MULTIPATH]); + + for_nexthops(fi) { + int attrlen = nhlen - sizeof(struct rtnexthop); + __le16 gw; + + if (attrlen < 0 || (nhlen -= nhp->rtnh_len) < 0) + return -EINVAL; + if (nhp->rtnh_ifindex && nhp->rtnh_ifindex != nh->nh_oif) + return 1; + if (attrlen) { + struct nlattr *gw_attr; + + gw_attr = nla_find((struct nlattr *) (nhp + 1), attrlen, RTA_GATEWAY); + gw = gw_attr ? nla_get_le16(gw_attr) : 0; + + if (gw && gw != nh->nh_gw) + return 1; + } + nhp = RTNH_NEXT(nhp); + } endfor_nexthops(fi); + + return 0; +} + +static inline size_t dn_fib_nlmsg_size(struct dn_fib_info *fi) +{ + size_t payload = NLMSG_ALIGN(sizeof(struct rtmsg)) + + nla_total_size(4) /* RTA_TABLE */ + + nla_total_size(2) /* RTA_DST */ + + nla_total_size(4) /* RTA_PRIORITY */ + + nla_total_size(TCP_CA_NAME_MAX); /* RTAX_CC_ALGO */ + + /* space for nested metrics */ + payload += nla_total_size((RTAX_MAX * nla_total_size(4))); + + if (fi->fib_nhs) { + /* Also handles the special case fib_nhs == 1 */ + + /* each nexthop is packed in an attribute */ + size_t nhsize = nla_total_size(sizeof(struct rtnexthop)); + + /* may contain a gateway attribute */ + nhsize += nla_total_size(4); + + /* all nexthops are packed in a nested attribute */ + payload += nla_total_size(fi->fib_nhs * nhsize); + } + + return payload; +} + +static int dn_fib_dump_info(struct sk_buff *skb, u32 portid, u32 seq, int event, + u32 tb_id, u8 type, u8 scope, void *dst, int dst_len, + struct dn_fib_info *fi, unsigned int flags) +{ + struct rtmsg *rtm; + struct nlmsghdr *nlh; + + nlh = nlmsg_put(skb, portid, seq, event, sizeof(*rtm), flags); + if (!nlh) + return -EMSGSIZE; + + rtm = nlmsg_data(nlh); + rtm->rtm_family = AF_DECnet; + rtm->rtm_dst_len = dst_len; + rtm->rtm_src_len = 0; + rtm->rtm_tos = 0; + rtm->rtm_table = tb_id; + rtm->rtm_flags = fi->fib_flags; + rtm->rtm_scope = scope; + rtm->rtm_type = type; + rtm->rtm_protocol = fi->fib_protocol; + + if (nla_put_u32(skb, RTA_TABLE, tb_id) < 0) + goto errout; + + if (rtm->rtm_dst_len && + nla_put(skb, RTA_DST, 2, dst) < 0) + goto errout; + + if (fi->fib_priority && + nla_put_u32(skb, RTA_PRIORITY, fi->fib_priority) < 0) + goto errout; + + if (rtnetlink_put_metrics(skb, fi->fib_metrics) < 0) + goto errout; + + if (fi->fib_nhs == 1) { + if (fi->fib_nh->nh_gw && + nla_put_le16(skb, RTA_GATEWAY, fi->fib_nh->nh_gw) < 0) + goto errout; + + if (fi->fib_nh->nh_oif && + nla_put_u32(skb, RTA_OIF, fi->fib_nh->nh_oif) < 0) + goto errout; + } + + if (fi->fib_nhs > 1) { + struct rtnexthop *nhp; + struct nlattr *mp_head; + + if (!(mp_head = nla_nest_start(skb, RTA_MULTIPATH))) + goto errout; + + for_nexthops(fi) { + if (!(nhp = nla_reserve_nohdr(skb, sizeof(*nhp)))) + goto errout; + + nhp->rtnh_flags = nh->nh_flags & 0xFF; + nhp->rtnh_hops = nh->nh_weight - 1; + nhp->rtnh_ifindex = nh->nh_oif; + + if (nh->nh_gw && + nla_put_le16(skb, RTA_GATEWAY, nh->nh_gw) < 0) + goto errout; + + nhp->rtnh_len = skb_tail_pointer(skb) - (unsigned char *)nhp; + } endfor_nexthops(fi); + + nla_nest_end(skb, mp_head); + } + + nlmsg_end(skb, nlh); + return 0; + +errout: + nlmsg_cancel(skb, nlh); + return -EMSGSIZE; +} + + +static void dn_rtmsg_fib(int event, struct dn_fib_node *f, int z, u32 tb_id, + struct nlmsghdr *nlh, struct netlink_skb_parms *req) +{ + struct sk_buff *skb; + u32 portid = req ? req->portid : 0; + int err = -ENOBUFS; + + skb = nlmsg_new(dn_fib_nlmsg_size(DN_FIB_INFO(f)), GFP_KERNEL); + if (skb == NULL) + goto errout; + + err = dn_fib_dump_info(skb, portid, nlh->nlmsg_seq, event, tb_id, + f->fn_type, f->fn_scope, &f->fn_key, z, + DN_FIB_INFO(f), 0); + if (err < 0) { + /* -EMSGSIZE implies BUG in dn_fib_nlmsg_size() */ + WARN_ON(err == -EMSGSIZE); + kfree_skb(skb); + goto errout; + } + rtnl_notify(skb, &init_net, portid, RTNLGRP_DECnet_ROUTE, nlh, GFP_KERNEL); + return; +errout: + if (err < 0) + rtnl_set_sk_err(&init_net, RTNLGRP_DECnet_ROUTE, err); +} + +static __inline__ int dn_hash_dump_bucket(struct sk_buff *skb, + struct netlink_callback *cb, + struct dn_fib_table *tb, + struct dn_zone *dz, + struct dn_fib_node *f) +{ + int i, s_i; + + s_i = cb->args[4]; + for(i = 0; f; i++, f = f->fn_next) { + if (i < s_i) + continue; + if (f->fn_state & DN_S_ZOMBIE) + continue; + if (dn_fib_dump_info(skb, NETLINK_CB(cb->skb).portid, + cb->nlh->nlmsg_seq, + RTM_NEWROUTE, + tb->n, + (f->fn_state & DN_S_ZOMBIE) ? 0 : f->fn_type, + f->fn_scope, &f->fn_key, dz->dz_order, + f->fn_info, NLM_F_MULTI) < 0) { + cb->args[4] = i; + return -1; + } + } + cb->args[4] = i; + return skb->len; +} + +static __inline__ int dn_hash_dump_zone(struct sk_buff *skb, + struct netlink_callback *cb, + struct dn_fib_table *tb, + struct dn_zone *dz) +{ + int h, s_h; + + s_h = cb->args[3]; + for(h = 0; h < dz->dz_divisor; h++) { + if (h < s_h) + continue; + if (h > s_h) + memset(&cb->args[4], 0, sizeof(cb->args) - 4*sizeof(cb->args[0])); + if (dz->dz_hash == NULL || dz->dz_hash[h] == NULL) + continue; + if (dn_hash_dump_bucket(skb, cb, tb, dz, dz->dz_hash[h]) < 0) { + cb->args[3] = h; + return -1; + } + } + cb->args[3] = h; + return skb->len; +} + +static int dn_fib_table_dump(struct dn_fib_table *tb, struct sk_buff *skb, + struct netlink_callback *cb) +{ + int m, s_m; + struct dn_zone *dz; + struct dn_hash *table = (struct dn_hash *)tb->data; + + s_m = cb->args[2]; + read_lock(&dn_fib_tables_lock); + for(dz = table->dh_zone_list, m = 0; dz; dz = dz->dz_next, m++) { + if (m < s_m) + continue; + if (m > s_m) + memset(&cb->args[3], 0, sizeof(cb->args) - 3*sizeof(cb->args[0])); + + if (dn_hash_dump_zone(skb, cb, tb, dz) < 0) { + cb->args[2] = m; + read_unlock(&dn_fib_tables_lock); + return -1; + } + } + read_unlock(&dn_fib_tables_lock); + cb->args[2] = m; + + return skb->len; +} + +int dn_fib_dump(struct sk_buff *skb, struct netlink_callback *cb) +{ + struct net *net = sock_net(skb->sk); + unsigned int h, s_h; + unsigned int e = 0, s_e; + struct dn_fib_table *tb; + int dumped = 0; + + if (!net_eq(net, &init_net)) + return 0; + + if (nlmsg_len(cb->nlh) >= sizeof(struct rtmsg) && + ((struct rtmsg *)nlmsg_data(cb->nlh))->rtm_flags&RTM_F_CLONED) + return dn_cache_dump(skb, cb); + + s_h = cb->args[0]; + s_e = cb->args[1]; + + for (h = s_h; h < DN_FIB_TABLE_HASHSZ; h++, s_h = 0) { + e = 0; + hlist_for_each_entry(tb, &dn_fib_table_hash[h], hlist) { + if (e < s_e) + goto next; + if (dumped) + memset(&cb->args[2], 0, sizeof(cb->args) - + 2 * sizeof(cb->args[0])); + if (tb->dump(tb, skb, cb) < 0) + goto out; + dumped = 1; +next: + e++; + } + } +out: + cb->args[1] = e; + cb->args[0] = h; + + return skb->len; +} + +static int dn_fib_table_insert(struct dn_fib_table *tb, struct rtmsg *r, struct nlattr *attrs[], + struct nlmsghdr *n, struct netlink_skb_parms *req) +{ + struct dn_hash *table = (struct dn_hash *)tb->data; + struct dn_fib_node *new_f, *f, **fp, **del_fp; + struct dn_zone *dz; + struct dn_fib_info *fi; + int z = r->rtm_dst_len; + int type = r->rtm_type; + dn_fib_key_t key; + int err; + + if (z > 16) + return -EINVAL; + + dz = table->dh_zones[z]; + if (!dz && !(dz = dn_new_zone(table, z))) + return -ENOBUFS; + + dz_key_0(key); + if (attrs[RTA_DST]) { + __le16 dst = nla_get_le16(attrs[RTA_DST]); + if (dst & ~DZ_MASK(dz)) + return -EINVAL; + key = dz_key(dst, dz); + } + + if ((fi = dn_fib_create_info(r, attrs, n, &err)) == NULL) + return err; + + if (dz->dz_nent > (dz->dz_divisor << 2) && + dz->dz_divisor > DN_MAX_DIVISOR && + (z==16 || (1<<z) > dz->dz_divisor)) + dn_rehash_zone(dz); + + fp = dn_chain_p(key, dz); + + DN_FIB_SCAN(f, fp) { + if (dn_key_leq(key, f->fn_key)) + break; + } + + del_fp = NULL; + + if (f && (f->fn_state & DN_S_ZOMBIE) && + dn_key_eq(f->fn_key, key)) { + del_fp = fp; + fp = &f->fn_next; + f = *fp; + goto create; + } + + DN_FIB_SCAN_KEY(f, fp, key) { + if (fi->fib_priority <= DN_FIB_INFO(f)->fib_priority) + break; + } + + if (f && dn_key_eq(f->fn_key, key) && + fi->fib_priority == DN_FIB_INFO(f)->fib_priority) { + struct dn_fib_node **ins_fp; + + err = -EEXIST; + if (n->nlmsg_flags & NLM_F_EXCL) + goto out; + + if (n->nlmsg_flags & NLM_F_REPLACE) { + del_fp = fp; + fp = &f->fn_next; + f = *fp; + goto replace; + } + + ins_fp = fp; + err = -EEXIST; + + DN_FIB_SCAN_KEY(f, fp, key) { + if (fi->fib_priority != DN_FIB_INFO(f)->fib_priority) + break; + if (f->fn_type == type && + f->fn_scope == r->rtm_scope && + DN_FIB_INFO(f) == fi) + goto out; + } + + if (!(n->nlmsg_flags & NLM_F_APPEND)) { + fp = ins_fp; + f = *fp; + } + } + +create: + err = -ENOENT; + if (!(n->nlmsg_flags & NLM_F_CREATE)) + goto out; + +replace: + err = -ENOBUFS; + new_f = kmem_cache_zalloc(dn_hash_kmem, GFP_KERNEL); + if (new_f == NULL) + goto out; + + new_f->fn_key = key; + new_f->fn_type = type; + new_f->fn_scope = r->rtm_scope; + DN_FIB_INFO(new_f) = fi; + + new_f->fn_next = f; + write_lock_bh(&dn_fib_tables_lock); + *fp = new_f; + write_unlock_bh(&dn_fib_tables_lock); + dz->dz_nent++; + + if (del_fp) { + f = *del_fp; + write_lock_bh(&dn_fib_tables_lock); + *del_fp = f->fn_next; + write_unlock_bh(&dn_fib_tables_lock); + + if (!(f->fn_state & DN_S_ZOMBIE)) + dn_rtmsg_fib(RTM_DELROUTE, f, z, tb->n, n, req); + if (f->fn_state & DN_S_ACCESSED) + dn_rt_cache_flush(-1); + dn_free_node(f); + dz->dz_nent--; + } else { + dn_rt_cache_flush(-1); + } + + dn_rtmsg_fib(RTM_NEWROUTE, new_f, z, tb->n, n, req); + + return 0; +out: + dn_fib_release_info(fi); + return err; +} + + +static int dn_fib_table_delete(struct dn_fib_table *tb, struct rtmsg *r, struct nlattr *attrs[], + struct nlmsghdr *n, struct netlink_skb_parms *req) +{ + struct dn_hash *table = (struct dn_hash*)tb->data; + struct dn_fib_node **fp, **del_fp, *f; + int z = r->rtm_dst_len; + struct dn_zone *dz; + dn_fib_key_t key; + int matched; + + + if (z > 16) + return -EINVAL; + + if ((dz = table->dh_zones[z]) == NULL) + return -ESRCH; + + dz_key_0(key); + if (attrs[RTA_DST]) { + __le16 dst = nla_get_le16(attrs[RTA_DST]); + if (dst & ~DZ_MASK(dz)) + return -EINVAL; + key = dz_key(dst, dz); + } + + fp = dn_chain_p(key, dz); + + DN_FIB_SCAN(f, fp) { + if (dn_key_eq(f->fn_key, key)) + break; + if (dn_key_leq(key, f->fn_key)) + return -ESRCH; + } + + matched = 0; + del_fp = NULL; + DN_FIB_SCAN_KEY(f, fp, key) { + struct dn_fib_info *fi = DN_FIB_INFO(f); + + if (f->fn_state & DN_S_ZOMBIE) + return -ESRCH; + + matched++; + + if (del_fp == NULL && + (!r->rtm_type || f->fn_type == r->rtm_type) && + (r->rtm_scope == RT_SCOPE_NOWHERE || f->fn_scope == r->rtm_scope) && + (!r->rtm_protocol || + fi->fib_protocol == r->rtm_protocol) && + dn_fib_nh_match(r, n, attrs, fi) == 0) + del_fp = fp; + } + + if (del_fp) { + f = *del_fp; + dn_rtmsg_fib(RTM_DELROUTE, f, z, tb->n, n, req); + + if (matched != 1) { + write_lock_bh(&dn_fib_tables_lock); + *del_fp = f->fn_next; + write_unlock_bh(&dn_fib_tables_lock); + + if (f->fn_state & DN_S_ACCESSED) + dn_rt_cache_flush(-1); + dn_free_node(f); + dz->dz_nent--; + } else { + f->fn_state |= DN_S_ZOMBIE; + if (f->fn_state & DN_S_ACCESSED) { + f->fn_state &= ~DN_S_ACCESSED; + dn_rt_cache_flush(-1); + } + if (++dn_fib_hash_zombies > 128) + dn_fib_flush(); + } + + return 0; + } + + return -ESRCH; +} + +static inline int dn_flush_list(struct dn_fib_node **fp, int z, struct dn_hash *table) +{ + int found = 0; + struct dn_fib_node *f; + + while((f = *fp) != NULL) { + struct dn_fib_info *fi = DN_FIB_INFO(f); + + if (fi && ((f->fn_state & DN_S_ZOMBIE) || (fi->fib_flags & RTNH_F_DEAD))) { + write_lock_bh(&dn_fib_tables_lock); + *fp = f->fn_next; + write_unlock_bh(&dn_fib_tables_lock); + + dn_free_node(f); + found++; + continue; + } + fp = &f->fn_next; + } + + return found; +} + +static int dn_fib_table_flush(struct dn_fib_table *tb) +{ + struct dn_hash *table = (struct dn_hash *)tb->data; + struct dn_zone *dz; + int found = 0; + + dn_fib_hash_zombies = 0; + for(dz = table->dh_zone_list; dz; dz = dz->dz_next) { + int i; + int tmp = 0; + for(i = dz->dz_divisor-1; i >= 0; i--) + tmp += dn_flush_list(&dz->dz_hash[i], dz->dz_order, table); + dz->dz_nent -= tmp; + found += tmp; + } + + return found; +} + +static int dn_fib_table_lookup(struct dn_fib_table *tb, const struct flowidn *flp, struct dn_fib_res *res) +{ + int err; + struct dn_zone *dz; + struct dn_hash *t = (struct dn_hash *)tb->data; + + read_lock(&dn_fib_tables_lock); + for(dz = t->dh_zone_list; dz; dz = dz->dz_next) { + struct dn_fib_node *f; + dn_fib_key_t k = dz_key(flp->daddr, dz); + + for(f = dz_chain(k, dz); f; f = f->fn_next) { + if (!dn_key_eq(k, f->fn_key)) { + if (dn_key_leq(k, f->fn_key)) + break; + else + continue; + } + + f->fn_state |= DN_S_ACCESSED; + + if (f->fn_state&DN_S_ZOMBIE) + continue; + + if (f->fn_scope < flp->flowidn_scope) + continue; + + err = dn_fib_semantic_match(f->fn_type, DN_FIB_INFO(f), flp, res); + + if (err == 0) { + res->type = f->fn_type; + res->scope = f->fn_scope; + res->prefixlen = dz->dz_order; + goto out; + } + if (err < 0) + goto out; + } + } + err = 1; +out: + read_unlock(&dn_fib_tables_lock); + return err; +} + + +struct dn_fib_table *dn_fib_get_table(u32 n, int create) +{ + struct dn_fib_table *t; + unsigned int h; + + if (n < RT_TABLE_MIN) + return NULL; + + if (n > RT_TABLE_MAX) + return NULL; + + h = n & (DN_FIB_TABLE_HASHSZ - 1); + rcu_read_lock(); + hlist_for_each_entry_rcu(t, &dn_fib_table_hash[h], hlist) { + if (t->n == n) { + rcu_read_unlock(); + return t; + } + } + rcu_read_unlock(); + + if (!create) + return NULL; + + if (in_interrupt()) { + net_dbg_ratelimited("DECnet: BUG! Attempt to create routing table from interrupt\n"); + return NULL; + } + + t = kzalloc(sizeof(struct dn_fib_table) + sizeof(struct dn_hash), + GFP_KERNEL); + if (t == NULL) + return NULL; + + t->n = n; + t->insert = dn_fib_table_insert; + t->delete = dn_fib_table_delete; + t->lookup = dn_fib_table_lookup; + t->flush = dn_fib_table_flush; + t->dump = dn_fib_table_dump; + hlist_add_head_rcu(&t->hlist, &dn_fib_table_hash[h]); + + return t; +} + +struct dn_fib_table *dn_fib_empty_table(void) +{ + u32 id; + + for(id = RT_TABLE_MIN; id <= RT_TABLE_MAX; id++) + if (dn_fib_get_table(id, 0) == NULL) + return dn_fib_get_table(id, 1); + return NULL; +} + +void dn_fib_flush(void) +{ + int flushed = 0; + struct dn_fib_table *tb; + unsigned int h; + + for (h = 0; h < DN_FIB_TABLE_HASHSZ; h++) { + hlist_for_each_entry(tb, &dn_fib_table_hash[h], hlist) + flushed += tb->flush(tb); + } + + if (flushed) + dn_rt_cache_flush(-1); +} + +void __init dn_fib_table_init(void) +{ + dn_hash_kmem = kmem_cache_create("dn_fib_info_cache", + sizeof(struct dn_fib_info), + 0, SLAB_HWCACHE_ALIGN, + NULL); +} + +void __exit dn_fib_table_cleanup(void) +{ + struct dn_fib_table *t; + struct hlist_node *next; + unsigned int h; + + write_lock(&dn_fib_tables_lock); + for (h = 0; h < DN_FIB_TABLE_HASHSZ; h++) { + hlist_for_each_entry_safe(t, next, &dn_fib_table_hash[h], + hlist) { + hlist_del(&t->hlist); + kfree(t); + } + } + write_unlock(&dn_fib_tables_lock); +} diff --git a/net/decnet/dn_timer.c b/net/decnet/dn_timer.c new file mode 100644 index 000000000..1d330fd43 --- /dev/null +++ b/net/decnet/dn_timer.c @@ -0,0 +1,103 @@ +/* + * DECnet An implementation of the DECnet protocol suite for the LINUX + * operating system. DECnet is implemented using the BSD Socket + * interface as the means of communication with the user level. + * + * DECnet Socket Timer Functions + * + * Author: Steve Whitehouse <SteveW@ACM.org> + * + * + * Changes: + * Steve Whitehouse : Made keepalive timer part of the same + * timer idea. + * Steve Whitehouse : Added checks for sk->sock_readers + * David S. Miller : New socket locking + * Steve Whitehouse : Timer grabs socket ref. + */ +#include <linux/net.h> +#include <linux/socket.h> +#include <linux/skbuff.h> +#include <linux/netdevice.h> +#include <linux/timer.h> +#include <linux/spinlock.h> +#include <net/sock.h> +#include <linux/atomic.h> +#include <linux/jiffies.h> +#include <net/flow.h> +#include <net/dn.h> + +/* + * Slow timer is for everything else (n * 500mS) + */ + +#define SLOW_INTERVAL (HZ/2) + +static void dn_slow_timer(unsigned long arg); + +void dn_start_slow_timer(struct sock *sk) +{ + setup_timer(&sk->sk_timer, dn_slow_timer, (unsigned long)sk); + sk_reset_timer(sk, &sk->sk_timer, jiffies + SLOW_INTERVAL); +} + +void dn_stop_slow_timer(struct sock *sk) +{ + sk_stop_timer(sk, &sk->sk_timer); +} + +static void dn_slow_timer(unsigned long arg) +{ + struct sock *sk = (struct sock *)arg; + struct dn_scp *scp = DN_SK(sk); + + bh_lock_sock(sk); + + if (sock_owned_by_user(sk)) { + sk_reset_timer(sk, &sk->sk_timer, jiffies + HZ / 10); + goto out; + } + + /* + * The persist timer is the standard slow timer used for retransmits + * in both connection establishment and disconnection as well as + * in the RUN state. The different states are catered for by changing + * the function pointer in the socket. Setting the timer to a value + * of zero turns it off. We allow the persist_fxn to turn the + * timer off in a permant way by returning non-zero, so that + * timer based routines may remove sockets. This is why we have a + * sock_hold()/sock_put() around the timer to prevent the socket + * going away in the middle. + */ + if (scp->persist && scp->persist_fxn) { + if (scp->persist <= SLOW_INTERVAL) { + scp->persist = 0; + + if (scp->persist_fxn(sk)) + goto out; + } else { + scp->persist -= SLOW_INTERVAL; + } + } + + /* + * Check for keepalive timeout. After the other timer 'cos if + * the previous timer caused a retransmit, we don't need to + * do this. scp->stamp is the last time that we sent a packet. + * The keepalive function sends a link service packet to the + * other end. If it remains unacknowledged, the standard + * socket timers will eventually shut the socket down. Each + * time we do this, scp->stamp will be updated, thus + * we won't try and send another until scp->keepalive has passed + * since the last successful transmission. + */ + if (scp->keepalive && scp->keepalive_fxn && (scp->state == DN_RUN)) { + if (time_after_eq(jiffies, scp->stamp + scp->keepalive)) + scp->keepalive_fxn(sk); + } + + sk_reset_timer(sk, &sk->sk_timer, jiffies + SLOW_INTERVAL); +out: + bh_unlock_sock(sk); + sock_put(sk); +} diff --git a/net/decnet/netfilter/Kconfig b/net/decnet/netfilter/Kconfig new file mode 100644 index 000000000..8d7c109d5 --- /dev/null +++ b/net/decnet/netfilter/Kconfig @@ -0,0 +1,16 @@ +# +# DECnet netfilter configuration +# + +menu "DECnet: Netfilter Configuration" + depends on DECNET && NETFILTER + depends on NETFILTER_ADVANCED + +config DECNET_NF_GRABULATOR + tristate "Routing message grabulator (for userland routing daemon)" + help + Enable this module if you want to use the userland DECnet routing + daemon. You will also need to enable routing support for DECnet + unless you just want to monitor routing messages from other nodes. + +endmenu diff --git a/net/decnet/netfilter/Makefile b/net/decnet/netfilter/Makefile new file mode 100644 index 000000000..255c1ae9d --- /dev/null +++ b/net/decnet/netfilter/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for DECnet netfilter modules +# + +obj-$(CONFIG_DECNET_NF_GRABULATOR) += dn_rtmsg.o + diff --git a/net/decnet/netfilter/dn_rtmsg.c b/net/decnet/netfilter/dn_rtmsg.c new file mode 100644 index 000000000..af34fc9bd --- /dev/null +++ b/net/decnet/netfilter/dn_rtmsg.c @@ -0,0 +1,159 @@ +/* + * DECnet An implementation of the DECnet protocol suite for the LINUX + * operating system. DECnet is implemented using the BSD Socket + * interface as the means of communication with the user level. + * + * DECnet Routing Message Grabulator + * + * (C) 2000 ChyGwyn Limited - http://www.chygwyn.com/ + * This code may be copied under the GPL v.2 or at your option + * any later version. + * + * Author: Steven Whitehouse <steve@chygwyn.com> + * + */ +#include <linux/module.h> +#include <linux/skbuff.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/netdevice.h> +#include <linux/netfilter.h> +#include <linux/spinlock.h> +#include <net/netlink.h> +#include <linux/netfilter_decnet.h> + +#include <net/sock.h> +#include <net/flow.h> +#include <net/dn.h> +#include <net/dn_route.h> + +static struct sock *dnrmg = NULL; + + +static struct sk_buff *dnrmg_build_message(struct sk_buff *rt_skb, int *errp) +{ + struct sk_buff *skb = NULL; + size_t size; + sk_buff_data_t old_tail; + struct nlmsghdr *nlh; + unsigned char *ptr; + struct nf_dn_rtmsg *rtm; + + size = NLMSG_ALIGN(rt_skb->len) + + NLMSG_ALIGN(sizeof(struct nf_dn_rtmsg)); + skb = nlmsg_new(size, GFP_ATOMIC); + if (!skb) { + *errp = -ENOMEM; + return NULL; + } + old_tail = skb->tail; + nlh = nlmsg_put(skb, 0, 0, 0, size, 0); + if (!nlh) { + kfree_skb(skb); + *errp = -ENOMEM; + return NULL; + } + rtm = (struct nf_dn_rtmsg *)nlmsg_data(nlh); + rtm->nfdn_ifindex = rt_skb->dev->ifindex; + ptr = NFDN_RTMSG(rtm); + skb_copy_from_linear_data(rt_skb, ptr, rt_skb->len); + nlh->nlmsg_len = skb->tail - old_tail; + return skb; +} + +static void dnrmg_send_peer(struct sk_buff *skb) +{ + struct sk_buff *skb2; + int status = 0; + int group = 0; + unsigned char flags = *skb->data; + + switch (flags & DN_RT_CNTL_MSK) { + case DN_RT_PKT_L1RT: + group = DNRNG_NLGRP_L1; + break; + case DN_RT_PKT_L2RT: + group = DNRNG_NLGRP_L2; + break; + default: + return; + } + + skb2 = dnrmg_build_message(skb, &status); + if (skb2 == NULL) + return; + NETLINK_CB(skb2).dst_group = group; + netlink_broadcast(dnrmg, skb2, 0, group, GFP_ATOMIC); +} + + +static unsigned int dnrmg_hook(const struct nf_hook_ops *ops, + struct sk_buff *skb, + const struct nf_hook_state *state) +{ + dnrmg_send_peer(skb); + return NF_ACCEPT; +} + + +#define RCV_SKB_FAIL(err) do { netlink_ack(skb, nlh, (err)); return; } while (0) + +static inline void dnrmg_receive_user_skb(struct sk_buff *skb) +{ + struct nlmsghdr *nlh = nlmsg_hdr(skb); + + if (nlh->nlmsg_len < sizeof(*nlh) || skb->len < nlh->nlmsg_len) + return; + + if (!netlink_capable(skb, CAP_NET_ADMIN)) + RCV_SKB_FAIL(-EPERM); + + /* Eventually we might send routing messages too */ + + RCV_SKB_FAIL(-EINVAL); +} + +static struct nf_hook_ops dnrmg_ops __read_mostly = { + .hook = dnrmg_hook, + .pf = NFPROTO_DECNET, + .hooknum = NF_DN_ROUTE, + .priority = NF_DN_PRI_DNRTMSG, +}; + +static int __init dn_rtmsg_init(void) +{ + int rv = 0; + struct netlink_kernel_cfg cfg = { + .groups = DNRNG_NLGRP_MAX, + .input = dnrmg_receive_user_skb, + }; + + dnrmg = netlink_kernel_create(&init_net, NETLINK_DNRTMSG, &cfg); + if (dnrmg == NULL) { + printk(KERN_ERR "dn_rtmsg: Cannot create netlink socket"); + return -ENOMEM; + } + + rv = nf_register_hook(&dnrmg_ops); + if (rv) { + netlink_kernel_release(dnrmg); + } + + return rv; +} + +static void __exit dn_rtmsg_fini(void) +{ + nf_unregister_hook(&dnrmg_ops); + netlink_kernel_release(dnrmg); +} + + +MODULE_DESCRIPTION("DECnet Routing Message Grabulator"); +MODULE_AUTHOR("Steven Whitehouse <steve@chygwyn.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_NET_PF_PROTO(PF_NETLINK, NETLINK_DNRTMSG); + +module_init(dn_rtmsg_init); +module_exit(dn_rtmsg_fini); + diff --git a/net/decnet/sysctl_net_decnet.c b/net/decnet/sysctl_net_decnet.c new file mode 100644 index 000000000..5325b541c --- /dev/null +++ b/net/decnet/sysctl_net_decnet.c @@ -0,0 +1,372 @@ +/* + * DECnet An implementation of the DECnet protocol suite for the LINUX + * operating system. DECnet is implemented using the BSD Socket + * interface as the means of communication with the user level. + * + * DECnet sysctl support functions + * + * Author: Steve Whitehouse <SteveW@ACM.org> + * + * + * Changes: + * Steve Whitehouse - C99 changes and default device handling + * Steve Whitehouse - Memory buffer settings, like the tcp ones + * + */ +#include <linux/mm.h> +#include <linux/sysctl.h> +#include <linux/fs.h> +#include <linux/netdevice.h> +#include <linux/string.h> +#include <net/neighbour.h> +#include <net/dst.h> +#include <net/flow.h> + +#include <asm/uaccess.h> + +#include <net/dn.h> +#include <net/dn_dev.h> +#include <net/dn_route.h> + + +int decnet_debug_level; +int decnet_time_wait = 30; +int decnet_dn_count = 1; +int decnet_di_count = 3; +int decnet_dr_count = 3; +int decnet_log_martians = 1; +int decnet_no_fc_max_cwnd = NSP_MIN_WINDOW; + +/* Reasonable defaults, I hope, based on tcp's defaults */ +long sysctl_decnet_mem[3] = { 768 << 3, 1024 << 3, 1536 << 3 }; +int sysctl_decnet_wmem[3] = { 4 * 1024, 16 * 1024, 128 * 1024 }; +int sysctl_decnet_rmem[3] = { 4 * 1024, 87380, 87380 * 2 }; + +#ifdef CONFIG_SYSCTL +extern int decnet_dst_gc_interval; +static int min_decnet_time_wait[] = { 5 }; +static int max_decnet_time_wait[] = { 600 }; +static int min_state_count[] = { 1 }; +static int max_state_count[] = { NSP_MAXRXTSHIFT }; +static int min_decnet_dst_gc_interval[] = { 1 }; +static int max_decnet_dst_gc_interval[] = { 60 }; +static int min_decnet_no_fc_max_cwnd[] = { NSP_MIN_WINDOW }; +static int max_decnet_no_fc_max_cwnd[] = { NSP_MAX_WINDOW }; +static char node_name[7] = "???"; + +static struct ctl_table_header *dn_table_header = NULL; + +/* + * ctype.h :-) + */ +#define ISNUM(x) (((x) >= '0') && ((x) <= '9')) +#define ISLOWER(x) (((x) >= 'a') && ((x) <= 'z')) +#define ISUPPER(x) (((x) >= 'A') && ((x) <= 'Z')) +#define ISALPHA(x) (ISLOWER(x) || ISUPPER(x)) +#define INVALID_END_CHAR(x) (ISNUM(x) || ISALPHA(x)) + +static void strip_it(char *str) +{ + for(;;) { + switch (*str) { + case ' ': + case '\n': + case '\r': + case ':': + *str = 0; + /* Fallthrough */ + case 0: + return; + } + str++; + } +} + +/* + * Simple routine to parse an ascii DECnet address + * into a network order address. + */ +static int parse_addr(__le16 *addr, char *str) +{ + __u16 area, node; + + while(*str && !ISNUM(*str)) str++; + + if (*str == 0) + return -1; + + area = (*str++ - '0'); + if (ISNUM(*str)) { + area *= 10; + area += (*str++ - '0'); + } + + if (*str++ != '.') + return -1; + + if (!ISNUM(*str)) + return -1; + + node = *str++ - '0'; + if (ISNUM(*str)) { + node *= 10; + node += (*str++ - '0'); + } + if (ISNUM(*str)) { + node *= 10; + node += (*str++ - '0'); + } + if (ISNUM(*str)) { + node *= 10; + node += (*str++ - '0'); + } + + if ((node > 1023) || (area > 63)) + return -1; + + if (INVALID_END_CHAR(*str)) + return -1; + + *addr = cpu_to_le16((area << 10) | node); + + return 0; +} + +static int dn_node_address_handler(struct ctl_table *table, int write, + void __user *buffer, + size_t *lenp, loff_t *ppos) +{ + char addr[DN_ASCBUF_LEN]; + size_t len; + __le16 dnaddr; + + if (!*lenp || (*ppos && !write)) { + *lenp = 0; + return 0; + } + + if (write) { + len = (*lenp < DN_ASCBUF_LEN) ? *lenp : (DN_ASCBUF_LEN-1); + + if (copy_from_user(addr, buffer, len)) + return -EFAULT; + + addr[len] = 0; + strip_it(addr); + + if (parse_addr(&dnaddr, addr)) + return -EINVAL; + + dn_dev_devices_off(); + + decnet_address = dnaddr; + + dn_dev_devices_on(); + + *ppos += len; + + return 0; + } + + dn_addr2asc(le16_to_cpu(decnet_address), addr); + len = strlen(addr); + addr[len++] = '\n'; + + if (len > *lenp) len = *lenp; + + if (copy_to_user(buffer, addr, len)) + return -EFAULT; + + *lenp = len; + *ppos += len; + + return 0; +} + +static int dn_def_dev_handler(struct ctl_table *table, int write, + void __user *buffer, + size_t *lenp, loff_t *ppos) +{ + size_t len; + struct net_device *dev; + char devname[17]; + + if (!*lenp || (*ppos && !write)) { + *lenp = 0; + return 0; + } + + if (write) { + if (*lenp > 16) + return -E2BIG; + + if (copy_from_user(devname, buffer, *lenp)) + return -EFAULT; + + devname[*lenp] = 0; + strip_it(devname); + + dev = dev_get_by_name(&init_net, devname); + if (dev == NULL) + return -ENODEV; + + if (dev->dn_ptr == NULL) { + dev_put(dev); + return -ENODEV; + } + + if (dn_dev_set_default(dev, 1)) { + dev_put(dev); + return -ENODEV; + } + *ppos += *lenp; + + return 0; + } + + dev = dn_dev_get_default(); + if (dev == NULL) { + *lenp = 0; + return 0; + } + + strcpy(devname, dev->name); + dev_put(dev); + len = strlen(devname); + devname[len++] = '\n'; + + if (len > *lenp) len = *lenp; + + if (copy_to_user(buffer, devname, len)) + return -EFAULT; + + *lenp = len; + *ppos += len; + + return 0; +} + +static struct ctl_table dn_table[] = { + { + .procname = "node_address", + .maxlen = 7, + .mode = 0644, + .proc_handler = dn_node_address_handler, + }, + { + .procname = "node_name", + .data = node_name, + .maxlen = 7, + .mode = 0644, + .proc_handler = proc_dostring, + }, + { + .procname = "default_device", + .maxlen = 16, + .mode = 0644, + .proc_handler = dn_def_dev_handler, + }, + { + .procname = "time_wait", + .data = &decnet_time_wait, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec_minmax, + .extra1 = &min_decnet_time_wait, + .extra2 = &max_decnet_time_wait + }, + { + .procname = "dn_count", + .data = &decnet_dn_count, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec_minmax, + .extra1 = &min_state_count, + .extra2 = &max_state_count + }, + { + .procname = "di_count", + .data = &decnet_di_count, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec_minmax, + .extra1 = &min_state_count, + .extra2 = &max_state_count + }, + { + .procname = "dr_count", + .data = &decnet_dr_count, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec_minmax, + .extra1 = &min_state_count, + .extra2 = &max_state_count + }, + { + .procname = "dst_gc_interval", + .data = &decnet_dst_gc_interval, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec_minmax, + .extra1 = &min_decnet_dst_gc_interval, + .extra2 = &max_decnet_dst_gc_interval + }, + { + .procname = "no_fc_max_cwnd", + .data = &decnet_no_fc_max_cwnd, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec_minmax, + .extra1 = &min_decnet_no_fc_max_cwnd, + .extra2 = &max_decnet_no_fc_max_cwnd + }, + { + .procname = "decnet_mem", + .data = &sysctl_decnet_mem, + .maxlen = sizeof(sysctl_decnet_mem), + .mode = 0644, + .proc_handler = proc_doulongvec_minmax + }, + { + .procname = "decnet_rmem", + .data = &sysctl_decnet_rmem, + .maxlen = sizeof(sysctl_decnet_rmem), + .mode = 0644, + .proc_handler = proc_dointvec, + }, + { + .procname = "decnet_wmem", + .data = &sysctl_decnet_wmem, + .maxlen = sizeof(sysctl_decnet_wmem), + .mode = 0644, + .proc_handler = proc_dointvec, + }, + { + .procname = "debug", + .data = &decnet_debug_level, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec, + }, + { } +}; + +void dn_register_sysctl(void) +{ + dn_table_header = register_net_sysctl(&init_net, "net/decnet", dn_table); +} + +void dn_unregister_sysctl(void) +{ + unregister_net_sysctl_table(dn_table_header); +} + +#else /* CONFIG_SYSCTL */ +void dn_unregister_sysctl(void) +{ +} +void dn_register_sysctl(void) +{ +} + +#endif |