/*
|
/*
|
* This module implements the (SPP-derived) Sequenced Packet eXchange
|
* This module implements the (SPP-derived) Sequenced Packet eXchange
|
* (SPX) protocol for Linux 2.1.X as specified in
|
* (SPX) protocol for Linux 2.1.X as specified in
|
* NetWare SPX Services Specification, Semantics and API
|
* NetWare SPX Services Specification, Semantics and API
|
* Revision: 1.00
|
* Revision: 1.00
|
* Revision Date: February 9, 1993
|
* Revision Date: February 9, 1993
|
*
|
*
|
* Developers:
|
* Developers:
|
* Jay Schulist <jschlst@samba.org>
|
* Jay Schulist <jschlst@samba.org>
|
* Jim Freeman <jfree@caldera.com>
|
* Jim Freeman <jfree@caldera.com>
|
*
|
*
|
* Changes:
|
* Changes:
|
* Alan Cox : Fixed an skb_unshare check for NULL
|
* Alan Cox : Fixed an skb_unshare check for NULL
|
* that crashed it under load. Renamed and
|
* that crashed it under load. Renamed and
|
* made static the ipx ops. Removed the hack
|
* made static the ipx ops. Removed the hack
|
* ipx methods interface. Dropped AF_SPX - its
|
* ipx methods interface. Dropped AF_SPX - its
|
* the wrong abstraction.
|
* the wrong abstraction.
|
* Eduardo Trapani : Added a check for the return value of
|
* Eduardo Trapani : Added a check for the return value of
|
* ipx_if_offset that crashed sock_alloc_send_skb.
|
* ipx_if_offset that crashed sock_alloc_send_skb.
|
* Added spx_datagram_poll() so that select()
|
* Added spx_datagram_poll() so that select()
|
* works now on SPX sockets. Added updating
|
* works now on SPX sockets. Added updating
|
* of the alloc count to follow rmt_seq.
|
* of the alloc count to follow rmt_seq.
|
*
|
*
|
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
* modify it under the terms of the GNU General Public License
|
* modify it under the terms of the GNU General Public License
|
* as published by the Free Software Foundation; either version
|
* as published by the Free Software Foundation; either version
|
* 2 of the License, or (at your option) any later version.
|
* 2 of the License, or (at your option) any later version.
|
*
|
*
|
* None of the authors or maintainers or their employers admit
|
* None of the authors or maintainers or their employers admit
|
* liability nor provide warranty for any of this software.
|
* liability nor provide warranty for any of this software.
|
* This material is provided "as is" and at no charge.
|
* This material is provided "as is" and at no charge.
|
*/
|
*/
|
|
|
#include <linux/module.h>
|
#include <linux/module.h>
|
#include <net/ipx.h>
|
#include <net/ipx.h>
|
#include <net/spx.h>
|
#include <net/spx.h>
|
#include <net/sock.h>
|
#include <net/sock.h>
|
#include <asm/byteorder.h>
|
#include <asm/byteorder.h>
|
#include <asm/uaccess.h>
|
#include <asm/uaccess.h>
|
#include <linux/uio.h>
|
#include <linux/uio.h>
|
#include <linux/unistd.h>
|
#include <linux/unistd.h>
|
#include <linux/poll.h>
|
#include <linux/poll.h>
|
|
|
static struct proto_ops *ipx_operations;
|
static struct proto_ops *ipx_operations;
|
static struct proto_ops spx_ops;
|
static struct proto_ops spx_ops;
|
static __u16 connids;
|
static __u16 connids;
|
|
|
/* Functions needed for SPX connection start up */
|
/* Functions needed for SPX connection start up */
|
static int spx_transmit(struct sock *sk,struct sk_buff *skb,int type,int len);
|
static int spx_transmit(struct sock *sk,struct sk_buff *skb,int type,int len);
|
static void spx_retransmit(unsigned long data);
|
static void spx_retransmit(unsigned long data);
|
static void spx_watchdog(unsigned long data);
|
static void spx_watchdog(unsigned long data);
|
void spx_rcv(struct sock *sk, int bytes);
|
void spx_rcv(struct sock *sk, int bytes);
|
|
|
extern void ipx_remove_socket(struct sock *sk);
|
extern void ipx_remove_socket(struct sock *sk);
|
|
|
/* Datagram poll: the same code as datagram_poll() in net/core
|
/* Datagram poll: the same code as datagram_poll() in net/core
|
but the right spx buffers are looked at and
|
but the right spx buffers are looked at and
|
there is no question on the type of the socket
|
there is no question on the type of the socket
|
*/
|
*/
|
static unsigned int spx_datagram_poll(struct file * file, struct socket *sock, poll_table *wait)
|
static unsigned int spx_datagram_poll(struct file * file, struct socket *sock, poll_table *wait)
|
{
|
{
|
struct sock *sk = sock->sk;
|
struct sock *sk = sock->sk;
|
struct spx_opt *pdata = &sk->tp_pinfo.af_spx;
|
struct spx_opt *pdata = &sk->tp_pinfo.af_spx;
|
unsigned int mask;
|
unsigned int mask;
|
|
|
poll_wait(file, sk->sleep, wait);
|
poll_wait(file, sk->sleep, wait);
|
mask = 0;
|
mask = 0;
|
|
|
/* exceptional events? */
|
/* exceptional events? */
|
if (sk->err || !skb_queue_empty(&sk->error_queue))
|
if (sk->err || !skb_queue_empty(&sk->error_queue))
|
mask |= POLLERR;
|
mask |= POLLERR;
|
if (sk->shutdown & RCV_SHUTDOWN)
|
if (sk->shutdown & RCV_SHUTDOWN)
|
mask |= POLLHUP;
|
mask |= POLLHUP;
|
|
|
/* readable? */
|
/* readable? */
|
if (!skb_queue_empty(&pdata->rcv_queue))
|
if (!skb_queue_empty(&pdata->rcv_queue))
|
mask |= POLLIN | POLLRDNORM;
|
mask |= POLLIN | POLLRDNORM;
|
|
|
/* Need to check for termination and startup */
|
/* Need to check for termination and startup */
|
if (sk->state==TCP_CLOSE)
|
if (sk->state==TCP_CLOSE)
|
mask |= POLLHUP;
|
mask |= POLLHUP;
|
/* connection hasn't started yet? */
|
/* connection hasn't started yet? */
|
if (sk->state == TCP_SYN_SENT)
|
if (sk->state == TCP_SYN_SENT)
|
return mask;
|
return mask;
|
|
|
/* writable? */
|
/* writable? */
|
if (sock_writeable(sk))
|
if (sock_writeable(sk))
|
mask |= POLLOUT | POLLWRNORM | POLLWRBAND;
|
mask |= POLLOUT | POLLWRNORM | POLLWRBAND;
|
else
|
else
|
set_bit(SOCK_ASYNC_NOSPACE,&sk->socket->flags);
|
set_bit(SOCK_ASYNC_NOSPACE,&sk->socket->flags);
|
|
|
return mask;
|
return mask;
|
}
|
}
|
|
|
/* Create the SPX specific data */
|
/* Create the SPX specific data */
|
static int spx_sock_init(struct sock *sk)
|
static int spx_sock_init(struct sock *sk)
|
{
|
{
|
struct spx_opt *pdata = &sk->tp_pinfo.af_spx;
|
struct spx_opt *pdata = &sk->tp_pinfo.af_spx;
|
|
|
pdata->state = SPX_CLOSED;
|
pdata->state = SPX_CLOSED;
|
pdata->sequence = 0;
|
pdata->sequence = 0;
|
pdata->acknowledge = 0;
|
pdata->acknowledge = 0;
|
pdata->source_connid = htons(connids);
|
pdata->source_connid = htons(connids);
|
pdata->rmt_seq = 0;
|
pdata->rmt_seq = 0;
|
connids++;
|
connids++;
|
|
|
pdata->owner = (void *)sk;
|
pdata->owner = (void *)sk;
|
pdata->sndbuf = sk->sndbuf;
|
pdata->sndbuf = sk->sndbuf;
|
|
|
pdata->watchdog.function = spx_watchdog;
|
pdata->watchdog.function = spx_watchdog;
|
pdata->watchdog.data = (unsigned long)sk;
|
pdata->watchdog.data = (unsigned long)sk;
|
pdata->wd_interval = VERIFY_TIMEOUT;
|
pdata->wd_interval = VERIFY_TIMEOUT;
|
pdata->retransmit.function = spx_retransmit;
|
pdata->retransmit.function = spx_retransmit;
|
pdata->retransmit.data = (unsigned long)sk;
|
pdata->retransmit.data = (unsigned long)sk;
|
pdata->retransmits = 0;
|
pdata->retransmits = 0;
|
pdata->retries = 0;
|
pdata->retries = 0;
|
pdata->max_retries = RETRY_COUNT;
|
pdata->max_retries = RETRY_COUNT;
|
|
|
skb_queue_head_init(&pdata->rcv_queue);
|
skb_queue_head_init(&pdata->rcv_queue);
|
skb_queue_head_init(&pdata->transmit_queue);
|
skb_queue_head_init(&pdata->transmit_queue);
|
skb_queue_head_init(&pdata->retransmit_queue);
|
skb_queue_head_init(&pdata->retransmit_queue);
|
|
|
return (0);
|
return (0);
|
}
|
}
|
|
|
static int spx_create(struct socket *sock, int protocol)
|
static int spx_create(struct socket *sock, int protocol)
|
{
|
{
|
struct sock *sk;
|
struct sock *sk;
|
|
|
/*
|
/*
|
* Called on connection receive so cannot be GFP_KERNEL
|
* Called on connection receive so cannot be GFP_KERNEL
|
*/
|
*/
|
|
|
sk = sk_alloc(PF_IPX, GFP_ATOMIC, 1);
|
sk = sk_alloc(PF_IPX, GFP_ATOMIC, 1);
|
if(sk == NULL)
|
if(sk == NULL)
|
return (-ENOMEM);
|
return (-ENOMEM);
|
|
|
switch(sock->type)
|
switch(sock->type)
|
{
|
{
|
case SOCK_SEQPACKET:
|
case SOCK_SEQPACKET:
|
sock->ops = &spx_ops;
|
sock->ops = &spx_ops;
|
break;
|
break;
|
default:
|
default:
|
sk_free(sk);
|
sk_free(sk);
|
return (-ESOCKTNOSUPPORT);
|
return (-ESOCKTNOSUPPORT);
|
}
|
}
|
|
|
sock_init_data(sock, sk);
|
sock_init_data(sock, sk);
|
spx_sock_init(sk);
|
spx_sock_init(sk);
|
sk->data_ready = spx_rcv;
|
sk->data_ready = spx_rcv;
|
sk->destruct = NULL;
|
sk->destruct = NULL;
|
sk->no_check = 1;
|
sk->no_check = 1;
|
|
|
MOD_INC_USE_COUNT;
|
MOD_INC_USE_COUNT;
|
|
|
return (0);
|
return (0);
|
}
|
}
|
|
|
|
|
void spx_close_socket(struct sock *sk)
|
void spx_close_socket(struct sock *sk)
|
{
|
{
|
struct spx_opt *pdata = &sk->tp_pinfo.af_spx;
|
struct spx_opt *pdata = &sk->tp_pinfo.af_spx;
|
|
|
pdata->state = SPX_CLOSED;
|
pdata->state = SPX_CLOSED;
|
sk->state = TCP_CLOSE;
|
sk->state = TCP_CLOSE;
|
del_timer(&pdata->retransmit);
|
del_timer(&pdata->retransmit);
|
del_timer(&pdata->watchdog);
|
del_timer(&pdata->watchdog);
|
}
|
}
|
|
|
void spx_destroy_socket(struct sock *sk)
|
void spx_destroy_socket(struct sock *sk)
|
{
|
{
|
struct spx_opt *pdata = &sk->tp_pinfo.af_spx;
|
struct spx_opt *pdata = &sk->tp_pinfo.af_spx;
|
struct sk_buff *skb;
|
struct sk_buff *skb;
|
|
|
ipx_remove_socket(sk);
|
ipx_remove_socket(sk);
|
while((skb = skb_dequeue(&sk->receive_queue)) != NULL)
|
while((skb = skb_dequeue(&sk->receive_queue)) != NULL)
|
kfree_skb(skb);
|
kfree_skb(skb);
|
while((skb = skb_dequeue(&pdata->transmit_queue)) != NULL)
|
while((skb = skb_dequeue(&pdata->transmit_queue)) != NULL)
|
kfree_skb(skb);
|
kfree_skb(skb);
|
while((skb = skb_dequeue(&pdata->retransmit_queue)) != NULL)
|
while((skb = skb_dequeue(&pdata->retransmit_queue)) != NULL)
|
kfree_skb(skb);
|
kfree_skb(skb);
|
while((skb = skb_dequeue(&pdata->rcv_queue)) != NULL)
|
while((skb = skb_dequeue(&pdata->rcv_queue)) != NULL)
|
kfree_skb(skb);
|
kfree_skb(skb);
|
|
|
sk_free(sk);
|
sk_free(sk);
|
MOD_DEC_USE_COUNT;
|
MOD_DEC_USE_COUNT;
|
}
|
}
|
|
|
/* Release an SPX socket */
|
/* Release an SPX socket */
|
static int spx_release(struct socket *sock)
|
static int spx_release(struct socket *sock)
|
{
|
{
|
struct sock *sk = sock->sk;
|
struct sock *sk = sock->sk;
|
struct spx_opt *pdata = &sk->tp_pinfo.af_spx;
|
struct spx_opt *pdata = &sk->tp_pinfo.af_spx;
|
|
|
if(sk == NULL)
|
if(sk == NULL)
|
return (0);
|
return (0);
|
if(!sk->dead)
|
if(!sk->dead)
|
sk->state_change(sk);
|
sk->state_change(sk);
|
sk->dead = 1;
|
sk->dead = 1;
|
|
|
if(pdata->state != SPX_CLOSED)
|
if(pdata->state != SPX_CLOSED)
|
{
|
{
|
spx_transmit(sk, NULL, DISCON, 0);
|
spx_transmit(sk, NULL, DISCON, 0);
|
spx_close_socket(sk);
|
spx_close_socket(sk);
|
}
|
}
|
|
|
sock->sk = NULL;
|
sock->sk = NULL;
|
sk->socket = NULL;
|
sk->socket = NULL;
|
spx_destroy_socket(sk);
|
spx_destroy_socket(sk);
|
|
|
return (0);
|
return (0);
|
}
|
}
|
|
|
/* Move a socket into listening state. */
|
/* Move a socket into listening state. */
|
static int spx_listen(struct socket *sock, int backlog)
|
static int spx_listen(struct socket *sock, int backlog)
|
{
|
{
|
struct sock *sk = sock->sk;
|
struct sock *sk = sock->sk;
|
|
|
if(sock->state != SS_UNCONNECTED)
|
if(sock->state != SS_UNCONNECTED)
|
return (-EINVAL);
|
return (-EINVAL);
|
if(sock->type != SOCK_SEQPACKET)
|
if(sock->type != SOCK_SEQPACKET)
|
return (-EOPNOTSUPP);
|
return (-EOPNOTSUPP);
|
if(sk->zapped != 0)
|
if(sk->zapped != 0)
|
return (-EAGAIN);
|
return (-EAGAIN);
|
|
|
sk->max_ack_backlog = backlog;
|
sk->max_ack_backlog = backlog;
|
if(sk->state != TCP_LISTEN)
|
if(sk->state != TCP_LISTEN)
|
{
|
{
|
sk->ack_backlog = 0;
|
sk->ack_backlog = 0;
|
sk->state = TCP_LISTEN;
|
sk->state = TCP_LISTEN;
|
}
|
}
|
sk->socket->flags |= __SO_ACCEPTCON;
|
sk->socket->flags |= __SO_ACCEPTCON;
|
|
|
return (0);
|
return (0);
|
}
|
}
|
|
|
/* Accept a pending SPX connection */
|
/* Accept a pending SPX connection */
|
static int spx_accept(struct socket *sock, struct socket *newsock, int flags)
|
static int spx_accept(struct socket *sock, struct socket *newsock, int flags)
|
{
|
{
|
struct sock *sk;
|
struct sock *sk;
|
struct sock *newsk;
|
struct sock *newsk;
|
struct sk_buff *skb;
|
struct sk_buff *skb;
|
int err;
|
int err;
|
|
|
if(sock->sk == NULL)
|
if(sock->sk == NULL)
|
return (-EINVAL);
|
return (-EINVAL);
|
sk = sock->sk;
|
sk = sock->sk;
|
|
|
if((sock->state != SS_UNCONNECTED) || !(sock->flags & __SO_ACCEPTCON))
|
if((sock->state != SS_UNCONNECTED) || !(sock->flags & __SO_ACCEPTCON))
|
return (-EINVAL);
|
return (-EINVAL);
|
if(sock->type != SOCK_SEQPACKET)
|
if(sock->type != SOCK_SEQPACKET)
|
return (-EOPNOTSUPP);
|
return (-EOPNOTSUPP);
|
if(sk->state != TCP_LISTEN)
|
if(sk->state != TCP_LISTEN)
|
return (-EINVAL);
|
return (-EINVAL);
|
|
|
cli();
|
cli();
|
do {
|
do {
|
skb = skb_dequeue(&sk->receive_queue);
|
skb = skb_dequeue(&sk->receive_queue);
|
if(skb == NULL)
|
if(skb == NULL)
|
{
|
{
|
if(flags & O_NONBLOCK)
|
if(flags & O_NONBLOCK)
|
{
|
{
|
sti();
|
sti();
|
return (-EWOULDBLOCK);
|
return (-EWOULDBLOCK);
|
}
|
}
|
interruptible_sleep_on(sk->sleep);
|
interruptible_sleep_on(sk->sleep);
|
if(signal_pending(current))
|
if(signal_pending(current))
|
{
|
{
|
sti();
|
sti();
|
return (-ERESTARTSYS);
|
return (-ERESTARTSYS);
|
}
|
}
|
}
|
}
|
} while (skb == NULL);
|
} while (skb == NULL);
|
|
|
newsk = skb->sk;
|
newsk = skb->sk;
|
newsk->pair = NULL;
|
newsk->pair = NULL;
|
sti();
|
sti();
|
|
|
err = spx_transmit(newsk, skb, CONACK, 0); /* Connection ACK */
|
err = spx_transmit(newsk, skb, CONACK, 0); /* Connection ACK */
|
if(err)
|
if(err)
|
return (err);
|
return (err);
|
|
|
/* Now attach up the new socket */
|
/* Now attach up the new socket */
|
sock->sk = NULL;
|
sock->sk = NULL;
|
sk->ack_backlog--;
|
sk->ack_backlog--;
|
newsock->sk = newsk;
|
newsock->sk = newsk;
|
newsk->state = TCP_ESTABLISHED;
|
newsk->state = TCP_ESTABLISHED;
|
newsk->protinfo.af_ipx.dest_addr = newsk->tp_pinfo.af_spx.dest_addr;
|
newsk->protinfo.af_ipx.dest_addr = newsk->tp_pinfo.af_spx.dest_addr;
|
|
|
return (0);
|
return (0);
|
}
|
}
|
|
|
/* Build a connection to an SPX socket */
|
/* Build a connection to an SPX socket */
|
static int spx_connect(struct socket *sock, struct sockaddr *uaddr,
|
static int spx_connect(struct socket *sock, struct sockaddr *uaddr,
|
int addr_len, int flags)
|
int addr_len, int flags)
|
{
|
{
|
struct sock *sk = sock->sk;
|
struct sock *sk = sock->sk;
|
struct spx_opt *pdata = &sk->tp_pinfo.af_spx;
|
struct spx_opt *pdata = &sk->tp_pinfo.af_spx;
|
struct sockaddr_ipx src;
|
struct sockaddr_ipx src;
|
struct sk_buff *skb;
|
struct sk_buff *skb;
|
int size, err;
|
int size, err;
|
|
|
size = sizeof(src);
|
size = sizeof(src);
|
err = ipx_operations->getname(sock, (struct sockaddr *)&src, &size, 0);
|
err = ipx_operations->getname(sock, (struct sockaddr *)&src, &size, 0);
|
if(err)
|
if(err)
|
return (err);
|
return (err);
|
|
|
pdata->source_addr.net = src.sipx_network;
|
pdata->source_addr.net = src.sipx_network;
|
memcpy(pdata->source_addr.node, src.sipx_node, IPX_NODE_LEN);
|
memcpy(pdata->source_addr.node, src.sipx_node, IPX_NODE_LEN);
|
pdata->source_addr.sock = (unsigned short)src.sipx_port;
|
pdata->source_addr.sock = (unsigned short)src.sipx_port;
|
|
|
err = ipx_operations->connect(sock, uaddr, addr_len, flags);
|
err = ipx_operations->connect(sock, uaddr, addr_len, flags);
|
if(err)
|
if(err)
|
return (err);
|
return (err);
|
|
|
pdata->dest_addr = sk->protinfo.af_ipx.dest_addr;
|
pdata->dest_addr = sk->protinfo.af_ipx.dest_addr;
|
pdata->state = SPX_CONNECTING;
|
pdata->state = SPX_CONNECTING;
|
sock->state = SS_CONNECTING;
|
sock->state = SS_CONNECTING;
|
sk->state = TCP_SYN_SENT;
|
sk->state = TCP_SYN_SENT;
|
|
|
/* Send Connection request */
|
/* Send Connection request */
|
err = spx_transmit(sk, NULL, CONREQ, 0);
|
err = spx_transmit(sk, NULL, CONREQ, 0);
|
if(err)
|
if(err)
|
return (err);
|
return (err);
|
|
|
cli();
|
cli();
|
do {
|
do {
|
skb = skb_dequeue(&sk->receive_queue);
|
skb = skb_dequeue(&sk->receive_queue);
|
if(skb == NULL)
|
if(skb == NULL)
|
{
|
{
|
if(flags & O_NONBLOCK)
|
if(flags & O_NONBLOCK)
|
{
|
{
|
sti();
|
sti();
|
return (-EWOULDBLOCK);
|
return (-EWOULDBLOCK);
|
}
|
}
|
interruptible_sleep_on(sk->sleep);
|
interruptible_sleep_on(sk->sleep);
|
if(signal_pending(current))
|
if(signal_pending(current))
|
{
|
{
|
sti();
|
sti();
|
return (-ERESTARTSYS);
|
return (-ERESTARTSYS);
|
}
|
}
|
}
|
}
|
} while (skb == NULL);
|
} while (skb == NULL);
|
|
|
if(pdata->state == SPX_CLOSED)
|
if(pdata->state == SPX_CLOSED)
|
{
|
{
|
sti();
|
sti();
|
del_timer(&pdata->watchdog);
|
del_timer(&pdata->watchdog);
|
return (-ETIMEDOUT);
|
return (-ETIMEDOUT);
|
}
|
}
|
|
|
sock->state = SS_CONNECTED;
|
sock->state = SS_CONNECTED;
|
sk->state = TCP_ESTABLISHED;
|
sk->state = TCP_ESTABLISHED;
|
kfree_skb(skb);
|
kfree_skb(skb);
|
sti();
|
sti();
|
|
|
return (0);
|
return (0);
|
}
|
}
|
|
|
/*
|
/*
|
* Calculate the timeout for a packet. Thankfully SPX has a large
|
* Calculate the timeout for a packet. Thankfully SPX has a large
|
* fudge factor (3/4 secs) and does not pay much attention to RTT.
|
* fudge factor (3/4 secs) and does not pay much attention to RTT.
|
* As we simply have a default retry time of 1*HZ and a max retry
|
* As we simply have a default retry time of 1*HZ and a max retry
|
* time of 5*HZ. Between those values we increase the timeout based
|
* time of 5*HZ. Between those values we increase the timeout based
|
* on the number of retransmit tries.
|
* on the number of retransmit tries.
|
*
|
*
|
* FixMe: This is quite fake, but will work for now. (JS)
|
* FixMe: This is quite fake, but will work for now. (JS)
|
*/
|
*/
|
static inline unsigned long spx_calc_rtt(int tries)
|
static inline unsigned long spx_calc_rtt(int tries)
|
{
|
{
|
if(tries < 1)
|
if(tries < 1)
|
return (RETRY_TIME);
|
return (RETRY_TIME);
|
if(tries > 5)
|
if(tries > 5)
|
return (MAX_RETRY_DELAY);
|
return (MAX_RETRY_DELAY);
|
return (tries * HZ);
|
return (tries * HZ);
|
}
|
}
|
|
|
static int spx_route_skb(struct spx_opt *pdata, struct sk_buff *skb, int type)
|
static int spx_route_skb(struct spx_opt *pdata, struct sk_buff *skb, int type)
|
{
|
{
|
struct sk_buff *skb2;
|
struct sk_buff *skb2;
|
int err = 0;
|
int err = 0;
|
|
|
skb = skb_unshare(skb, GFP_ATOMIC);
|
skb = skb_unshare(skb, GFP_ATOMIC);
|
if(skb == NULL)
|
if(skb == NULL)
|
return (-ENOBUFS);
|
return (-ENOBUFS);
|
|
|
switch(type)
|
switch(type)
|
{
|
{
|
case (CONREQ):
|
case (CONREQ):
|
case (DATA):
|
case (DATA):
|
if(!skb_queue_empty(&pdata->retransmit_queue))
|
if(!skb_queue_empty(&pdata->retransmit_queue))
|
{
|
{
|
skb_queue_tail(&pdata->transmit_queue, skb);
|
skb_queue_tail(&pdata->transmit_queue, skb);
|
return 0;
|
return 0;
|
}
|
}
|
|
|
case (TQUEUE):
|
case (TQUEUE):
|
pdata->retransmit.expires = jiffies + spx_calc_rtt(0);
|
pdata->retransmit.expires = jiffies + spx_calc_rtt(0);
|
add_timer(&pdata->retransmit);
|
add_timer(&pdata->retransmit);
|
|
|
skb2 = skb_clone(skb, GFP_NOIO);
|
skb2 = skb_clone(skb, GFP_NOIO);
|
if(skb2 == NULL)
|
if(skb2 == NULL)
|
return -ENOBUFS;
|
return -ENOBUFS;
|
skb_queue_tail(&pdata->retransmit_queue, skb2);
|
skb_queue_tail(&pdata->retransmit_queue, skb2);
|
|
|
case (ACK):
|
case (ACK):
|
case (CONACK):
|
case (CONACK):
|
case (WDREQ):
|
case (WDREQ):
|
case (WDACK):
|
case (WDACK):
|
case (DISCON):
|
case (DISCON):
|
case (DISACK):
|
case (DISACK):
|
case (RETRAN):
|
case (RETRAN):
|
default:
|
default:
|
/* Send data */
|
/* Send data */
|
err = ipxrtr_route_skb(skb);
|
err = ipxrtr_route_skb(skb);
|
if(err)
|
if(err)
|
kfree_skb(skb);
|
kfree_skb(skb);
|
}
|
}
|
|
|
return (err);
|
return (err);
|
}
|
}
|
|
|
/* SPX packet transmit engine */
|
/* SPX packet transmit engine */
|
static int spx_transmit(struct sock *sk, struct sk_buff *skb, int type, int len)
|
static int spx_transmit(struct sock *sk, struct sk_buff *skb, int type, int len)
|
{
|
{
|
struct spx_opt *pdata = &sk->tp_pinfo.af_spx;
|
struct spx_opt *pdata = &sk->tp_pinfo.af_spx;
|
struct ipxspxhdr *ipxh;
|
struct ipxspxhdr *ipxh;
|
unsigned long flags;
|
unsigned long flags;
|
int err;
|
int err;
|
|
|
if(skb == NULL)
|
if(skb == NULL)
|
{
|
{
|
int offset = ipx_if_offset(pdata->dest_addr.net);
|
int offset = ipx_if_offset(pdata->dest_addr.net);
|
int size = offset + sizeof(struct ipxspxhdr);
|
int size = offset + sizeof(struct ipxspxhdr);
|
|
|
if (offset < 0) /* ENETUNREACH */
|
if (offset < 0) /* ENETUNREACH */
|
return(-ENETUNREACH);
|
return(-ENETUNREACH);
|
|
|
save_flags(flags);
|
save_flags(flags);
|
cli();
|
cli();
|
skb = sock_alloc_send_skb(sk, size, 0, &err);
|
skb = sock_alloc_send_skb(sk, size, 0, &err);
|
if(skb == NULL) {
|
if(skb == NULL) {
|
restore_flags(flags);
|
restore_flags(flags);
|
return (-ENOMEM);
|
return (-ENOMEM);
|
}
|
}
|
skb_reserve(skb, offset);
|
skb_reserve(skb, offset);
|
skb->h.raw = skb->nh.raw = skb_put(skb,sizeof(struct ipxspxhdr));
|
skb->h.raw = skb->nh.raw = skb_put(skb,sizeof(struct ipxspxhdr));
|
restore_flags(flags);
|
restore_flags(flags);
|
}
|
}
|
|
|
/* IPX header */
|
/* IPX header */
|
ipxh = (struct ipxspxhdr *)skb->nh.raw;
|
ipxh = (struct ipxspxhdr *)skb->nh.raw;
|
ipxh->ipx.ipx_checksum = 0xFFFF;
|
ipxh->ipx.ipx_checksum = 0xFFFF;
|
ipxh->ipx.ipx_pktsize = htons(SPX_SYS_PKT_LEN);
|
ipxh->ipx.ipx_pktsize = htons(SPX_SYS_PKT_LEN);
|
ipxh->ipx.ipx_tctrl = 0;
|
ipxh->ipx.ipx_tctrl = 0;
|
ipxh->ipx.ipx_type = IPX_TYPE_SPX;
|
ipxh->ipx.ipx_type = IPX_TYPE_SPX;
|
ipxh->ipx.ipx_dest = pdata->dest_addr;
|
ipxh->ipx.ipx_dest = pdata->dest_addr;
|
ipxh->ipx.ipx_source = pdata->source_addr;
|
ipxh->ipx.ipx_source = pdata->source_addr;
|
|
|
/* SPX header */
|
/* SPX header */
|
ipxh->spx.dtype = 0;
|
ipxh->spx.dtype = 0;
|
ipxh->spx.sequence = htons(pdata->sequence);
|
ipxh->spx.sequence = htons(pdata->sequence);
|
ipxh->spx.ackseq = htons(pdata->rmt_seq);
|
ipxh->spx.ackseq = htons(pdata->rmt_seq);
|
ipxh->spx.sconn = pdata->source_connid;
|
ipxh->spx.sconn = pdata->source_connid;
|
ipxh->spx.dconn = pdata->dest_connid;
|
ipxh->spx.dconn = pdata->dest_connid;
|
ipxh->spx.allocseq = htons(pdata->alloc);
|
ipxh->spx.allocseq = htons(pdata->alloc);
|
|
|
/* Reset/Set WD timer */
|
/* Reset/Set WD timer */
|
mod_timer(&pdata->watchdog, jiffies+VERIFY_TIMEOUT);
|
mod_timer(&pdata->watchdog, jiffies+VERIFY_TIMEOUT);
|
|
|
switch(type)
|
switch(type)
|
{
|
{
|
case (DATA): /* Data */
|
case (DATA): /* Data */
|
ipxh->ipx.ipx_pktsize = htons(SPX_SYS_PKT_LEN + len);
|
ipxh->ipx.ipx_pktsize = htons(SPX_SYS_PKT_LEN + len);
|
ipxh->spx.cctl = (CCTL_ACK | CCTL_EOM);
|
ipxh->spx.cctl = (CCTL_ACK | CCTL_EOM);
|
pdata->sequence++;
|
pdata->sequence++;
|
break;
|
break;
|
|
|
case (ACK): /* ACK */
|
case (ACK): /* ACK */
|
pdata->rmt_seq++;
|
pdata->rmt_seq++;
|
case (WDACK): /* WD ACK */
|
case (WDACK): /* WD ACK */
|
case (CONACK): /* Connection ACK */
|
case (CONACK): /* Connection ACK */
|
ipxh->spx.cctl = CCTL_SYS;
|
ipxh->spx.cctl = CCTL_SYS;
|
ipxh->spx.ackseq = htons(pdata->rmt_seq);
|
ipxh->spx.ackseq = htons(pdata->rmt_seq);
|
break;
|
break;
|
|
|
case (CONREQ): /* Connection Request */
|
case (CONREQ): /* Connection Request */
|
del_timer(&pdata->watchdog);
|
del_timer(&pdata->watchdog);
|
case (WDREQ): /* WD Request */
|
case (WDREQ): /* WD Request */
|
pdata->source_connid = htons(connids++);
|
pdata->source_connid = htons(connids++);
|
pdata->dest_connid = 0xFFFF;
|
pdata->dest_connid = 0xFFFF;
|
pdata->alloc = 3 + pdata->rmt_seq;
|
pdata->alloc = 3 + pdata->rmt_seq;
|
ipxh->spx.cctl = (CCTL_ACK | CCTL_SYS);
|
ipxh->spx.cctl = (CCTL_ACK | CCTL_SYS);
|
ipxh->spx.sconn = pdata->source_connid;
|
ipxh->spx.sconn = pdata->source_connid;
|
ipxh->spx.dconn = pdata->dest_connid;
|
ipxh->spx.dconn = pdata->dest_connid;
|
ipxh->spx.allocseq = htons(pdata->alloc);
|
ipxh->spx.allocseq = htons(pdata->alloc);
|
break;
|
break;
|
|
|
case (DISCON): /* Informed Disconnect */
|
case (DISCON): /* Informed Disconnect */
|
ipxh->spx.cctl = CCTL_ACK;
|
ipxh->spx.cctl = CCTL_ACK;
|
ipxh->spx.dtype = SPX_DTYPE_ECONN;
|
ipxh->spx.dtype = SPX_DTYPE_ECONN;
|
break;
|
break;
|
|
|
case (DISACK): /* Informed Disconnect ACK */
|
case (DISACK): /* Informed Disconnect ACK */
|
ipxh->spx.cctl = 0;
|
ipxh->spx.cctl = 0;
|
ipxh->spx.dtype = SPX_DTYPE_ECACK;
|
ipxh->spx.dtype = SPX_DTYPE_ECACK;
|
ipxh->spx.sequence = 0;
|
ipxh->spx.sequence = 0;
|
ipxh->spx.ackseq = htons(pdata->rmt_seq++);
|
ipxh->spx.ackseq = htons(pdata->rmt_seq++);
|
break;
|
break;
|
|
|
default:
|
default:
|
return (-EOPNOTSUPP);
|
return (-EOPNOTSUPP);
|
}
|
}
|
|
|
/* Send data */
|
/* Send data */
|
return (spx_route_skb(pdata, skb, type));
|
return (spx_route_skb(pdata, skb, type));
|
}
|
}
|
|
|
/* Check the state of the connection and send a WD request if needed. */
|
/* Check the state of the connection and send a WD request if needed. */
|
static void spx_watchdog(unsigned long data)
|
static void spx_watchdog(unsigned long data)
|
{
|
{
|
struct sock *sk = (struct sock*)data;
|
struct sock *sk = (struct sock*)data;
|
struct spx_opt *pdata = &sk->tp_pinfo.af_spx;
|
struct spx_opt *pdata = &sk->tp_pinfo.af_spx;
|
|
|
del_timer(&pdata->watchdog);
|
del_timer(&pdata->watchdog);
|
if(pdata->state == SPX_CLOSED)
|
if(pdata->state == SPX_CLOSED)
|
return;
|
return;
|
if(pdata->retries > pdata->max_retries)
|
if(pdata->retries > pdata->max_retries)
|
{
|
{
|
spx_close_socket(sk); /* Unilateral Abort */
|
spx_close_socket(sk); /* Unilateral Abort */
|
return;
|
return;
|
}
|
}
|
|
|
/* Send WD request */
|
/* Send WD request */
|
spx_transmit(sk, NULL, WDREQ, 0);
|
spx_transmit(sk, NULL, WDREQ, 0);
|
pdata->retries++;
|
pdata->retries++;
|
|
|
return;
|
return;
|
}
|
}
|
|
|
static void spx_retransmit(unsigned long data)
|
static void spx_retransmit(unsigned long data)
|
{
|
{
|
struct sock *sk = (struct sock*)data;
|
struct sock *sk = (struct sock*)data;
|
struct spx_opt *pdata = &sk->tp_pinfo.af_spx;
|
struct spx_opt *pdata = &sk->tp_pinfo.af_spx;
|
struct sk_buff *skb;
|
struct sk_buff *skb;
|
unsigned long flags;
|
unsigned long flags;
|
int err;
|
int err;
|
|
|
del_timer(&pdata->retransmit);
|
del_timer(&pdata->retransmit);
|
if(pdata->state == SPX_CLOSED)
|
if(pdata->state == SPX_CLOSED)
|
return;
|
return;
|
if(pdata->retransmits > RETRY_COUNT)
|
if(pdata->retransmits > RETRY_COUNT)
|
{
|
{
|
spx_close_socket(sk); /* Unilateral Abort */
|
spx_close_socket(sk); /* Unilateral Abort */
|
return;
|
return;
|
}
|
}
|
|
|
/* Need to leave skb on the queue, aye the fear */
|
/* Need to leave skb on the queue, aye the fear */
|
save_flags(flags);
|
save_flags(flags);
|
cli();
|
cli();
|
skb = skb_peek(&pdata->retransmit_queue);
|
skb = skb_peek(&pdata->retransmit_queue);
|
if(skb_cloned(skb))
|
if(skb_cloned(skb))
|
skb = skb_copy(skb, GFP_ATOMIC);
|
skb = skb_copy(skb, GFP_ATOMIC);
|
else
|
else
|
skb = skb_clone(skb, GFP_ATOMIC);
|
skb = skb_clone(skb, GFP_ATOMIC);
|
restore_flags(flags);
|
restore_flags(flags);
|
|
|
pdata->retransmit.expires = jiffies + spx_calc_rtt(pdata->retransmits);
|
pdata->retransmit.expires = jiffies + spx_calc_rtt(pdata->retransmits);
|
add_timer(&pdata->retransmit);
|
add_timer(&pdata->retransmit);
|
|
|
err = spx_route_skb(pdata, skb, RETRAN);
|
err = spx_route_skb(pdata, skb, RETRAN);
|
pdata->retransmits++;
|
pdata->retransmits++;
|
|
|
return;
|
return;
|
}
|
}
|
|
|
/* Check packet for retransmission, ConReqAck aware */
|
/* Check packet for retransmission, ConReqAck aware */
|
static int spx_retransmit_chk(struct spx_opt *pdata, int ackseq, int type)
|
static int spx_retransmit_chk(struct spx_opt *pdata, int ackseq, int type)
|
{
|
{
|
struct ipxspxhdr *ipxh;
|
struct ipxspxhdr *ipxh;
|
struct sk_buff *skb;
|
struct sk_buff *skb;
|
|
|
skb = skb_dequeue(&pdata->retransmit_queue);
|
skb = skb_dequeue(&pdata->retransmit_queue);
|
if(!skb)
|
if(!skb)
|
return (-ENOENT);
|
return (-ENOENT);
|
|
|
/* Check Data/ACK seq */
|
/* Check Data/ACK seq */
|
switch(type)
|
switch(type)
|
{
|
{
|
case ACK: /* Check Sequence, Should == 1 */
|
case ACK: /* Check Sequence, Should == 1 */
|
ipxh = (struct ipxspxhdr *)skb->nh.raw;
|
ipxh = (struct ipxspxhdr *)skb->nh.raw;
|
if(!(ntohs(ipxh->spx.sequence) - htons(ackseq)))
|
if(!(ntohs(ipxh->spx.sequence) - htons(ackseq)))
|
break;
|
break;
|
|
|
case CONACK:
|
case CONACK:
|
del_timer(&pdata->retransmit);
|
del_timer(&pdata->retransmit);
|
pdata->retransmits = 0;
|
pdata->retransmits = 0;
|
kfree_skb(skb);
|
kfree_skb(skb);
|
if(skb_queue_empty(&pdata->retransmit_queue))
|
if(skb_queue_empty(&pdata->retransmit_queue))
|
{
|
{
|
skb = skb_dequeue(&pdata->transmit_queue);
|
skb = skb_dequeue(&pdata->transmit_queue);
|
if(skb != NULL)
|
if(skb != NULL)
|
spx_route_skb(pdata, skb, TQUEUE);
|
spx_route_skb(pdata, skb, TQUEUE);
|
}
|
}
|
return (0);
|
return (0);
|
}
|
}
|
|
|
skb_queue_head(&pdata->retransmit_queue, skb);
|
skb_queue_head(&pdata->retransmit_queue, skb);
|
return (-1);
|
return (-1);
|
}
|
}
|
|
|
/* SPX packet receive engine */
|
/* SPX packet receive engine */
|
void spx_rcv(struct sock *sk, int bytes)
|
void spx_rcv(struct sock *sk, int bytes)
|
{
|
{
|
struct sk_buff *skb;
|
struct sk_buff *skb;
|
struct ipxspxhdr *ipxh;
|
struct ipxspxhdr *ipxh;
|
struct spx_opt *pdata = &sk->tp_pinfo.af_spx;
|
struct spx_opt *pdata = &sk->tp_pinfo.af_spx;
|
|
|
skb = skb_dequeue(&sk->receive_queue);
|
skb = skb_dequeue(&sk->receive_queue);
|
if(skb == NULL)
|
if(skb == NULL)
|
return;
|
return;
|
ipxh = (struct ipxspxhdr *)skb->nh.raw;
|
ipxh = (struct ipxspxhdr *)skb->nh.raw;
|
|
|
/* Can't receive on a closed connection */
|
/* Can't receive on a closed connection */
|
if((pdata->state == SPX_CLOSED) && (ipxh->spx.sequence != 0))
|
if((pdata->state == SPX_CLOSED) && (ipxh->spx.sequence != 0))
|
goto toss_skb;
|
goto toss_skb;
|
if(ntohs(ipxh->ipx.ipx_pktsize) < SPX_SYS_PKT_LEN)
|
if(ntohs(ipxh->ipx.ipx_pktsize) < SPX_SYS_PKT_LEN)
|
goto toss_skb;
|
goto toss_skb;
|
if(ipxh->ipx.ipx_type != IPX_TYPE_SPX)
|
if(ipxh->ipx.ipx_type != IPX_TYPE_SPX)
|
goto toss_skb;
|
goto toss_skb;
|
if(ntohs(ipxh->spx.ackseq) > pdata->sequence)
|
if(ntohs(ipxh->spx.ackseq) > pdata->sequence)
|
goto toss_skb;
|
goto toss_skb;
|
|
|
/* Reset WD timer on any received packet */
|
/* Reset WD timer on any received packet */
|
del_timer(&pdata->watchdog);
|
del_timer(&pdata->watchdog);
|
pdata->retries = 0;
|
pdata->retries = 0;
|
pdata->watchdog.expires = jiffies + ABORT_TIMEOUT;
|
pdata->watchdog.expires = jiffies + ABORT_TIMEOUT;
|
add_timer(&pdata->watchdog);
|
add_timer(&pdata->watchdog);
|
|
|
switch(ipxh->spx.cctl)
|
switch(ipxh->spx.cctl)
|
{
|
{
|
case (CCTL_SYS | CCTL_ACK):
|
case (CCTL_SYS | CCTL_ACK):
|
if((ipxh->spx.sequence == 0) /* ConReq */
|
if((ipxh->spx.sequence == 0) /* ConReq */
|
&& (ipxh->spx.ackseq == 0)
|
&& (ipxh->spx.ackseq == 0)
|
&& (ipxh->spx.dconn == 0xFFFF))
|
&& (ipxh->spx.dconn == 0xFFFF))
|
{
|
{
|
pdata->state = SPX_CONNECTED;
|
pdata->state = SPX_CONNECTED;
|
pdata->dest_addr = ipxh->ipx.ipx_source;
|
pdata->dest_addr = ipxh->ipx.ipx_source;
|
pdata->source_addr = ipxh->ipx.ipx_dest;
|
pdata->source_addr = ipxh->ipx.ipx_dest;
|
pdata->dest_connid = ipxh->spx.sconn;
|
pdata->dest_connid = ipxh->spx.sconn;
|
pdata->alloc = 3 + ntohs(ipxh->spx.sequence);
|
pdata->alloc = 3 + ntohs(ipxh->spx.sequence);
|
|
|
skb_queue_tail(&sk->receive_queue, skb);
|
skb_queue_tail(&sk->receive_queue, skb);
|
wake_up_interruptible(sk->sleep);
|
wake_up_interruptible(sk->sleep);
|
}
|
}
|
else /* WD Request */
|
else /* WD Request */
|
spx_transmit(sk, skb, WDACK, 0);
|
spx_transmit(sk, skb, WDACK, 0);
|
goto finish;
|
goto finish;
|
|
|
case CCTL_SYS: /* ACK */
|
case CCTL_SYS: /* ACK */
|
if((ipxh->spx.dtype == 0) /* ConReq ACK */
|
if((ipxh->spx.dtype == 0) /* ConReq ACK */
|
&& (ipxh->spx.sconn != 0xFFFF)
|
&& (ipxh->spx.sconn != 0xFFFF)
|
&& (ipxh->spx.dconn != 0xFFFF)
|
&& (ipxh->spx.dconn != 0xFFFF)
|
&& (ipxh->spx.sequence == 0)
|
&& (ipxh->spx.sequence == 0)
|
&& (ipxh->spx.ackseq == 0)
|
&& (ipxh->spx.ackseq == 0)
|
&& (pdata->state != SPX_CONNECTED))
|
&& (pdata->state != SPX_CONNECTED))
|
{
|
{
|
pdata->state = SPX_CONNECTED;
|
pdata->state = SPX_CONNECTED;
|
pdata->dest_connid = ipxh->spx.sconn;
|
pdata->dest_connid = ipxh->spx.sconn;
|
|
|
if(spx_retransmit_chk(pdata, 0, CONACK) < 0)
|
if(spx_retransmit_chk(pdata, 0, CONACK) < 0)
|
goto toss_skb;
|
goto toss_skb;
|
|
|
skb_queue_tail(&sk->receive_queue, skb);
|
skb_queue_tail(&sk->receive_queue, skb);
|
wake_up_interruptible(sk->sleep);
|
wake_up_interruptible(sk->sleep);
|
goto finish;
|
goto finish;
|
}
|
}
|
|
|
spx_retransmit_chk(pdata, ipxh->spx.ackseq, ACK);
|
spx_retransmit_chk(pdata, ipxh->spx.ackseq, ACK);
|
goto toss_skb;
|
goto toss_skb;
|
|
|
case (CCTL_ACK):
|
case (CCTL_ACK):
|
/* Informed Disconnect */
|
/* Informed Disconnect */
|
if(ipxh->spx.dtype == SPX_DTYPE_ECONN)
|
if(ipxh->spx.dtype == SPX_DTYPE_ECONN)
|
{
|
{
|
|
|
spx_transmit(sk, skb, DISACK, 0);
|
spx_transmit(sk, skb, DISACK, 0);
|
spx_close_socket(sk);
|
spx_close_socket(sk);
|
goto finish;
|
goto finish;
|
}
|
}
|
/* Fall through */
|
/* Fall through */
|
|
|
default:
|
default:
|
if(ntohs(ipxh->spx.sequence) == pdata->rmt_seq)
|
if(ntohs(ipxh->spx.sequence) == pdata->rmt_seq)
|
{
|
{
|
pdata->rmt_seq = ntohs(ipxh->spx.sequence);
|
pdata->rmt_seq = ntohs(ipxh->spx.sequence);
|
pdata->rmt_ack = ntohs(ipxh->spx.ackseq);
|
pdata->rmt_ack = ntohs(ipxh->spx.ackseq);
|
pdata->alloc = pdata->rmt_seq + 3;
|
pdata->alloc = pdata->rmt_seq + 3;
|
if(pdata->rmt_ack > 0 || pdata->rmt_ack == 0)
|
if(pdata->rmt_ack > 0 || pdata->rmt_ack == 0)
|
spx_retransmit_chk(pdata,pdata->rmt_ack, ACK);
|
spx_retransmit_chk(pdata,pdata->rmt_ack, ACK);
|
|
|
skb_queue_tail(&pdata->rcv_queue, skb);
|
skb_queue_tail(&pdata->rcv_queue, skb);
|
wake_up_interruptible(sk->sleep);
|
wake_up_interruptible(sk->sleep);
|
if(ipxh->spx.cctl&CCTL_ACK)
|
if(ipxh->spx.cctl&CCTL_ACK)
|
spx_transmit(sk, NULL, ACK, 0);
|
spx_transmit(sk, NULL, ACK, 0);
|
goto finish;
|
goto finish;
|
}
|
}
|
|
|
if(ipxh->spx.dtype == SPX_DTYPE_ECACK)
|
if(ipxh->spx.dtype == SPX_DTYPE_ECACK)
|
{
|
{
|
if(pdata->state != SPX_CLOSED)
|
if(pdata->state != SPX_CLOSED)
|
spx_close_socket(sk);
|
spx_close_socket(sk);
|
goto toss_skb;
|
goto toss_skb;
|
}
|
}
|
}
|
}
|
|
|
toss_skb: /* Catch All */
|
toss_skb: /* Catch All */
|
kfree_skb(skb);
|
kfree_skb(skb);
|
finish:
|
finish:
|
return;
|
return;
|
}
|
}
|
|
|
/* Get message/packet data from user-land */
|
/* Get message/packet data from user-land */
|
static int spx_sendmsg(struct socket *sock, struct msghdr *msg, int len,
|
static int spx_sendmsg(struct socket *sock, struct msghdr *msg, int len,
|
struct scm_cookie *scm)
|
struct scm_cookie *scm)
|
{
|
{
|
struct sock *sk = sock->sk;
|
struct sock *sk = sock->sk;
|
int flags = msg->msg_flags;
|
int flags = msg->msg_flags;
|
struct sk_buff *skb;
|
struct sk_buff *skb;
|
int err, offset, size;
|
int err, offset, size;
|
|
|
if(len > 534)
|
if(len > 534)
|
return (-EMSGSIZE);
|
return (-EMSGSIZE);
|
if(sk->zapped)
|
if(sk->zapped)
|
return (-ENOTCONN); /* Socket not bound */
|
return (-ENOTCONN); /* Socket not bound */
|
if(flags&~MSG_DONTWAIT)
|
if(flags&~MSG_DONTWAIT)
|
return (-EINVAL);
|
return (-EINVAL);
|
|
|
offset = ipx_if_offset(sk->tp_pinfo.af_spx.dest_addr.net);
|
offset = ipx_if_offset(sk->tp_pinfo.af_spx.dest_addr.net);
|
size = offset + sizeof(struct ipxspxhdr) + len;
|
size = offset + sizeof(struct ipxspxhdr) + len;
|
|
|
cli();
|
cli();
|
skb = sock_alloc_send_skb(sk, size, flags&MSG_DONTWAIT, &err);
|
skb = sock_alloc_send_skb(sk, size, flags&MSG_DONTWAIT, &err);
|
sti();
|
sti();
|
if(skb == NULL)
|
if(skb == NULL)
|
return (err);
|
return (err);
|
|
|
skb->sk = sk;
|
skb->sk = sk;
|
skb_reserve(skb, offset);
|
skb_reserve(skb, offset);
|
skb->h.raw = skb->nh.raw = skb_put(skb, sizeof(struct ipxspxhdr));
|
skb->h.raw = skb->nh.raw = skb_put(skb, sizeof(struct ipxspxhdr));
|
|
|
err = memcpy_fromiovec(skb_put(skb, len), msg->msg_iov, len);
|
err = memcpy_fromiovec(skb_put(skb, len), msg->msg_iov, len);
|
if(err)
|
if(err)
|
{
|
{
|
kfree_skb(skb);
|
kfree_skb(skb);
|
return (-EFAULT);
|
return (-EFAULT);
|
}
|
}
|
|
|
err = spx_transmit(sk, skb, DATA, len);
|
err = spx_transmit(sk, skb, DATA, len);
|
if(err)
|
if(err)
|
return (-EAGAIN);
|
return (-EAGAIN);
|
|
|
return (len);
|
return (len);
|
}
|
}
|
|
|
/* Send message/packet data to user-land */
|
/* Send message/packet data to user-land */
|
static int spx_recvmsg(struct socket *sock, struct msghdr *msg, int size,
|
static int spx_recvmsg(struct socket *sock, struct msghdr *msg, int size,
|
int flags, struct scm_cookie *scm)
|
int flags, struct scm_cookie *scm)
|
{
|
{
|
struct sk_buff *skb;
|
struct sk_buff *skb;
|
struct ipxspxhdr *ispxh;
|
struct ipxspxhdr *ispxh;
|
struct sock *sk = sock->sk;
|
struct sock *sk = sock->sk;
|
struct spx_opt *pdata = &sk->tp_pinfo.af_spx;
|
struct spx_opt *pdata = &sk->tp_pinfo.af_spx;
|
struct sockaddr_ipx *sipx = (struct sockaddr_ipx *)msg->msg_name;
|
struct sockaddr_ipx *sipx = (struct sockaddr_ipx *)msg->msg_name;
|
int copied, err;
|
int copied, err;
|
|
|
if(sk->zapped)
|
if(sk->zapped)
|
return (-ENOTCONN); /* Socket not bound */
|
return (-ENOTCONN); /* Socket not bound */
|
|
|
lock_sock(sk);
|
lock_sock(sk);
|
restart:
|
restart:
|
while(skb_queue_empty(&pdata->rcv_queue)) /* No data */
|
while(skb_queue_empty(&pdata->rcv_queue)) /* No data */
|
{
|
{
|
/* Socket errors? */
|
/* Socket errors? */
|
err = sock_error(sk);
|
err = sock_error(sk);
|
if(err)
|
if(err)
|
return (err);
|
return (err);
|
|
|
/* Socket shut down? */
|
/* Socket shut down? */
|
if(sk->shutdown & RCV_SHUTDOWN)
|
if(sk->shutdown & RCV_SHUTDOWN)
|
return (-ESHUTDOWN);
|
return (-ESHUTDOWN);
|
|
|
/* handle signals */
|
/* handle signals */
|
if(signal_pending(current))
|
if(signal_pending(current))
|
return (-ERESTARTSYS);
|
return (-ERESTARTSYS);
|
|
|
/* User doesn't want to wait */
|
/* User doesn't want to wait */
|
if(flags&MSG_DONTWAIT)
|
if(flags&MSG_DONTWAIT)
|
return (-EAGAIN);
|
return (-EAGAIN);
|
|
|
release_sock(sk);
|
release_sock(sk);
|
save_flags(flags);
|
save_flags(flags);
|
cli();
|
cli();
|
if(skb_peek(&pdata->rcv_queue) == NULL)
|
if(skb_peek(&pdata->rcv_queue) == NULL)
|
interruptible_sleep_on(sk->sleep);
|
interruptible_sleep_on(sk->sleep);
|
restore_flags(flags);
|
restore_flags(flags);
|
lock_sock(sk);
|
lock_sock(sk);
|
}
|
}
|
|
|
skb = skb_dequeue(&pdata->rcv_queue);
|
skb = skb_dequeue(&pdata->rcv_queue);
|
if(skb == NULL)
|
if(skb == NULL)
|
goto restart;
|
goto restart;
|
|
|
ispxh = (struct ipxspxhdr *)skb->nh.raw;
|
ispxh = (struct ipxspxhdr *)skb->nh.raw;
|
copied = ntohs(ispxh->ipx.ipx_pktsize) - SPX_SYS_PKT_LEN;
|
copied = ntohs(ispxh->ipx.ipx_pktsize) - SPX_SYS_PKT_LEN;
|
if(copied > size)
|
if(copied > size)
|
{
|
{
|
copied = size;
|
copied = size;
|
msg->msg_flags |= MSG_TRUNC;
|
msg->msg_flags |= MSG_TRUNC;
|
}
|
}
|
|
|
err = memcpy_toiovec(msg->msg_iov, skb->nh.raw+SPX_SYS_PKT_LEN, copied);
|
err = memcpy_toiovec(msg->msg_iov, skb->nh.raw+SPX_SYS_PKT_LEN, copied);
|
if(err)
|
if(err)
|
return (-EFAULT);
|
return (-EFAULT);
|
|
|
msg->msg_namelen = sizeof(*sipx);
|
msg->msg_namelen = sizeof(*sipx);
|
if(sipx)
|
if(sipx)
|
{
|
{
|
sipx->sipx_family = AF_IPX;
|
sipx->sipx_family = AF_IPX;
|
sipx->sipx_port = ispxh->ipx.ipx_source.sock;
|
sipx->sipx_port = ispxh->ipx.ipx_source.sock;
|
memcpy(sipx->sipx_node,ispxh->ipx.ipx_source.node,IPX_NODE_LEN);
|
memcpy(sipx->sipx_node,ispxh->ipx.ipx_source.node,IPX_NODE_LEN);
|
sipx->sipx_network = ispxh->ipx.ipx_source.net;
|
sipx->sipx_network = ispxh->ipx.ipx_source.net;
|
sipx->sipx_type = ispxh->ipx.ipx_type;
|
sipx->sipx_type = ispxh->ipx.ipx_type;
|
}
|
}
|
kfree_skb(skb);
|
kfree_skb(skb);
|
release_sock(sk);
|
release_sock(sk);
|
|
|
return (copied);
|
return (copied);
|
}
|
}
|
|
|
/*
|
/*
|
* Functions which just wrap their IPX cousins
|
* Functions which just wrap their IPX cousins
|
*/
|
*/
|
|
|
static int spx_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
|
static int spx_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
|
{
|
{
|
int err;
|
int err;
|
err = ipx_operations->bind(sock, uaddr, addr_len);
|
err = ipx_operations->bind(sock, uaddr, addr_len);
|
return (err);
|
return (err);
|
}
|
}
|
|
|
static int spx_getname (struct socket *sock, struct sockaddr *uaddr,
|
static int spx_getname (struct socket *sock, struct sockaddr *uaddr,
|
int *usockaddr_len, int peer)
|
int *usockaddr_len, int peer)
|
{
|
{
|
int err;
|
int err;
|
err = ipx_operations->getname(sock, uaddr, usockaddr_len, peer);
|
err = ipx_operations->getname(sock, uaddr, usockaddr_len, peer);
|
return (err);
|
return (err);
|
}
|
}
|
|
|
static int spx_ioctl (struct socket *sock, unsigned int cmd,
|
static int spx_ioctl (struct socket *sock, unsigned int cmd,
|
unsigned long arg)
|
unsigned long arg)
|
{
|
{
|
int err;
|
int err;
|
err = ipx_operations->ioctl(sock, cmd, arg);
|
err = ipx_operations->ioctl(sock, cmd, arg);
|
return (err);
|
return (err);
|
}
|
}
|
|
|
static int spx_setsockopt(struct socket *sock, int level, int optname,
|
static int spx_setsockopt(struct socket *sock, int level, int optname,
|
char *optval, int optlen)
|
char *optval, int optlen)
|
{
|
{
|
int err;
|
int err;
|
err = ipx_operations->setsockopt(sock, level, optname, optval, optlen);
|
err = ipx_operations->setsockopt(sock, level, optname, optval, optlen);
|
return (err);
|
return (err);
|
}
|
}
|
|
|
static int spx_getsockopt(struct socket *sock, int level, int optname,
|
static int spx_getsockopt(struct socket *sock, int level, int optname,
|
char *optval, int *optlen)
|
char *optval, int *optlen)
|
{
|
{
|
int err;
|
int err;
|
err = ipx_operations->getsockopt(sock, level, optname, optval, optlen);
|
err = ipx_operations->getsockopt(sock, level, optname, optval, optlen);
|
return (err);
|
return (err);
|
}
|
}
|
|
|
static struct proto_ops SOCKOPS_WRAPPED(spx_ops) = {
|
static struct proto_ops SOCKOPS_WRAPPED(spx_ops) = {
|
family: PF_IPX,
|
family: PF_IPX,
|
|
|
release: spx_release,
|
release: spx_release,
|
bind: spx_bind,
|
bind: spx_bind,
|
connect: spx_connect,
|
connect: spx_connect,
|
socketpair: sock_no_socketpair,
|
socketpair: sock_no_socketpair,
|
accept: spx_accept,
|
accept: spx_accept,
|
getname: spx_getname,
|
getname: spx_getname,
|
poll: spx_datagram_poll,
|
poll: spx_datagram_poll,
|
ioctl: spx_ioctl,
|
ioctl: spx_ioctl,
|
listen: spx_listen,
|
listen: spx_listen,
|
shutdown: sock_no_shutdown,
|
shutdown: sock_no_shutdown,
|
setsockopt: spx_setsockopt,
|
setsockopt: spx_setsockopt,
|
getsockopt: spx_getsockopt,
|
getsockopt: spx_getsockopt,
|
sendmsg: spx_sendmsg,
|
sendmsg: spx_sendmsg,
|
recvmsg: spx_recvmsg,
|
recvmsg: spx_recvmsg,
|
mmap: sock_no_mmap,
|
mmap: sock_no_mmap,
|
sendpage: sock_no_sendpage,
|
sendpage: sock_no_sendpage,
|
};
|
};
|
|
|
#include <linux/smp_lock.h>
|
#include <linux/smp_lock.h>
|
SOCKOPS_WRAP(spx, PF_IPX);
|
SOCKOPS_WRAP(spx, PF_IPX);
|
|
|
static struct net_proto_family spx_family_ops = {
|
static struct net_proto_family spx_family_ops = {
|
family: PF_IPX,
|
family: PF_IPX,
|
create: spx_create,
|
create: spx_create,
|
};
|
};
|
|
|
static char banner[] __initdata = KERN_INFO "NET4: Sequenced Packet eXchange (SPX) 0.02 for Linux NET4.0\n";
|
static char banner[] __initdata = KERN_INFO "NET4: Sequenced Packet eXchange (SPX) 0.02 for Linux NET4.0\n";
|
|
|
static int __init spx_proto_init(void)
|
static int __init spx_proto_init(void)
|
{
|
{
|
int error;
|
int error;
|
|
|
connids = (__u16)jiffies; /* initalize random */
|
connids = (__u16)jiffies; /* initalize random */
|
|
|
error = ipx_register_spx(&ipx_operations, &spx_family_ops);
|
error = ipx_register_spx(&ipx_operations, &spx_family_ops);
|
if (error)
|
if (error)
|
printk(KERN_ERR "SPX: unable to register with IPX.\n");
|
printk(KERN_ERR "SPX: unable to register with IPX.\n");
|
|
|
/* route socket(PF_IPX, SOCK_SEQPACKET) calls through spx_create() */
|
/* route socket(PF_IPX, SOCK_SEQPACKET) calls through spx_create() */
|
|
|
printk(banner);
|
printk(banner);
|
return 0;
|
return 0;
|
}
|
}
|
module_init(spx_proto_init);
|
module_init(spx_proto_init);
|
|
|
static void __exit spx_proto_finito(void)
|
static void __exit spx_proto_finito(void)
|
{
|
{
|
ipx_unregister_spx();
|
ipx_unregister_spx();
|
return;
|
return;
|
}
|
}
|
module_exit(spx_proto_finito);
|
module_exit(spx_proto_finito);
|
|
|