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