/*****************************************************************************

 @(#) $Id: af_ss7.c,v 0.7.2.5 2000/10/05 10:23:04 brian Exp $

 -----------------------------------------------------------------------------

 Copyright (C) 1997, 1998, 1999, 2000  Brian Bidulock <bidulock@dallas.net>

 All Rights Reserved.

 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.

 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.

 You should have received a copy of the GNU General Public License along with
 this program; if not, write to the Free Software Foundation, Inc., 675 Mass
 Ave, Cambridge, MA 02139, USA.

 Last Modified $Date: 2000/10/05 10:23:04 $ by $Author: brian $

 -----------------------------------------------------------------------------

 $Log: af_ss7.c,v $
 Revision 0.7.2.5  2000/10/05 10:23:04  brian
 Some work on link management and test.

 Revision 0.7.2.5  2000/10/03 00:53:54  brian
 Some work on link management and test.

 Revision 0.7.2.4  2000/09/13 13:47:06  brian
 A lot of work in state machine: haven't check compile yet,
 just wanting to save work for later.

 Revision 0.7.2.3  2000/09/11 20:20:47  brian
 Cleaned up routing structure.

 Revision 0.7.2.2  2000/09/11 14:08:18  brian
 Remove and updated some files for better structure.  Most compile now...

 Revision 0.7.2.1  2000/09/11 07:21:34  brian
 Doing a lot of work in socket code files.

 Revision 0.7  2000/09/07 11:12:13  brian
 Initial import of OpenSS7.

 Revision 1.2  2000/09/07 10:51:51  brian
 Got these files going.

 Revision 1.1.1.1  2000/09/05 11:00:20  brian
 Initial import of new OpenSS7 stack for Linux.

 *****************************************************************************/


/*
 *  This is the protocol routines for the entire SS7
 *  protocol.  It is repsonsible for initialization of the
 *  SS7 protocol in the kernel.  This is a separate module
 *  which must be loaded before other ss7 modules (link,
 *  net, sccp) are loaded.  It is responsible for
 *  registering the protocol options and socket creation
 *  functions and the device notifiers for the SS7 stack.
 */

#include <linux/module.h>
#include <linux/config.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/socket.h>
#include <linux/in.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/string.h>
#include <linux/sockios.h>
#include <linux/net.h>
#include <linux/fcntl.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 <asm/uaccess.h>
#include <asm/system.h>

#include <linux/skbuff.h>
#include <net/protocol.h>
#include <net/sock.h>

#include "af_ss7.h"
#include "mtp.h"
#include "mtp_route.h"
#include "sccp.h"
#include "../../include/linux/ss7link.h"


#ifdef CONFIG_KMOD
#include <linux/kmod.h>
#endif

static struct proto_ops mtp_ops;
static struct proto_ops sccp_cl_ops;
static struct proto_ops sccp_co_ops;

/*
 *  Set socket options on any ss7 socket.
 */
static int ss7_setsockopt(struct socket *sock, int level, int optname, char *optval, int optlen)
{
    struct sock *sk = sock->sk;
    if (sk->prot->setsockopt==NULL)
        return (-EOPNOTSUPP);
    return sk->prot->setsockopt(sk,level,optname,optval,optlen);
}

static int ss7_getsockopt(struct socket *sock, int level, int optname, char *optval, int *optlen)
{
    struct sock *sk = sock->sk;
    if (sk->prot->getsockopt==NULL)
        return (-EOPNOTSUPP);
    return sk->prot->getsockopt(sk,level,optname,optval,optlen);
}

static int ss7_listen(struct socket *sock, int backlog)
{
    (void)&ss7_listen;
    (void)sock;
    (void)backlog;
    return (-EOPNOTSUPP);   /* use later for SCCP class 2 and 3 */
}

/*
 *  A new socket has been created with the socket() command
 *  by the user and they have specified PF_SS7 as the
 *  protocol family.  The `protocol' argument normally has
 *  the IEEE 802.3 network order protocol type for PACKET
 *  sockets.  To use the services of the PACKET sockets,
 *  this number must be assigned.  Later we might want to
 *  send SS7 over Ethernet (helluva concept!) so we might
 *  want to pick an 802.3 number.  Experimental numbers are
 *  0101 thru 01FF.  We need the FF on the end for the
 *  SOCK_PACKET code in Linux?  I went ahead and assigned an
 *  ETH_P_SS7 in <linux/if_ether.h> with the other dummy
 *  types so that packet taps will work correctly.  The type
 *  ETH_P_SS7 should be assigned by the driver to their
 *  sk_buff's so that the packet redirector works.  But
 *  let's not use that here: we can use that for the
 *  packet_socket().
 *
 *  Here, since we are in AF_SS7/PF_SS7 and not
 *  AF_INET/PF_INET we can do whatever we would like with
 *  the protocol field.  I will define the protocol fields
 *  in <net/ss7.h>
 */

static int ss7_create(struct socket *sock, int protocol)
{
    struct sock *sk;
    int err = 0;
    int socktype = sock->type;
    struct proto *prot;
    (void)&ss7_create;

    if (socktype == SOCK_PACKET) {
        if (net_families[PF_PACKET]==NULL)
        {
#if defined(CONFIG_KMOD) && defined(CONFIG_PACKET_MODULE)
            char module_name[30];
            sprintf(module_name, "net-pf-%d", PF_PACKET);
            request_mdoule(module_name);
#endif
            if (net_families[PF_PACKET] == NULL)
                return -ESOCKTNOSUPPORT;
        }
        return net_families[PF_PACKET]->create(sock, protocol);
    }

    sock->state = SS_UNCONNECTED;

    if ((sk = sk_alloc(PF_SS7, GFP_KERNEL, 1)) == NULL) return -ENOBUFS;
    MOD_INC_USE_COUNT;

    switch (protocol) {
        case 0: protocol = SS7_PROTO_NTWK;  /* assume she wanted MTP */
        case SS7_PROTO_NTWK:
            switch (socktype) {
                case SOCK_NTWK_ISEQ:
                case SOCK_NTWK_NSEQ:
                    prot = &mtp_prot;
                    sock->ops = &mtp_ops;
                    break;
                case SOCK_NTWK_RAW:     /* deal with raw later */
                default:
                    err = -ESOCKTNOSUPPORT;
                    break;
            }
            break;
        case SS7_PROTO_SCCP:
            switch (socktype) {
                case SOCK_SCCP_CC_0:
                case SOCK_SCCP_CC_1:
                    prot = &sccp_prot;
                    sock->ops = &sccp_cl_ops;
                    break;
                case SOCK_SCCP_CC_2:
                case SOCK_SCCP_CC_3:
                    prot = &sccp_prot;
                    sock->ops = &sccp_co_ops;
                    break;
                default:
                    err = -ESOCKTNOSUPPORT;
                    break;
            }
            break;
        default:
            err = -EPROTONOSUPPORT;
            break;
    }
    if (err) { sk_free(sk); MOD_DEC_USE_COUNT; return err; }

    sock_init_data(sock,sk);
    sk->state = 0;
    sk->destruct = NULL;
    sk->zapped = 0;

    sk->family = PF_SS7;
    sk->protocol = protocol;
    sk->prot = prot;
    sk->backlog_rcv = prot->backlog_rcv;

    if (sk->prot->init) {
        err = sk->prot->init(sk);
        if (err) {
            if (sk->prot->destroy)
                sk->prot->destroy(sk);
            sk_free(sk);
            MOD_DEC_USE_COUNT;
            return (err);
        }
    }
    return (0);
}

/*
 *  This socket has just be released.  Gee, we just made it!
 *  :)
 */
static int ss7_release(struct socket *sock, struct socket *peersock)
{
    struct sock *sk = sock->sk;

    if (sk) {
        long timeout;

        if (sock->state != SS_UNCONNECTED)
            sock->state = SS_DISCONNECTING;
        sk->state_change(sk);

        /*
         *  If linger is set, we don't return until the
         *  close is complete.  Otherwise we return
         *  immediately. The actually closing is done the
         *  same either way.
         *
         *  If the close is due to the process exiting, we
         *  never linger..
         */
        timeout = 0;
        if (sk->linger && !(current->flags & PF_EXITING)) {
            timeout = HZ * sk->lingertime;
            if (!timeout)
                timeout = MAX_SCHEDULE_TIMEOUT;
        }
        sock->sk = NULL;
        sk->socket = NULL;
        /*
         *  Call the protocol's release mechanism.
         */
        sk->prot->close(sk, timeout);
    }
    return(0);
}

/*
 *  This bind is rather generic.  Unlike IP, SS7 has
 *  different bind requirements for MTP and SCCP, so the
 *  specific binding has been moved down to the prot
 *  methods.
 */

static int ss7_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
{
	struct sock *sk=sock->sk;
	unsigned short snum;
	int err = 0;

        /* If the socket has its own bind function then use
         * it. (RAW) */
        if(sk->prot->bind)
            if ((err = sk->prot->bind(sk, uaddr, addr_len)))
                return (err);
        /*
         *  Ask the protocol (in this case MTP) whether we
         *  can bind to this port number.  In our case this
         *  checks the ss7mtp_cb associated with the address
         *  (sk->saddr) and check whether the user part is
         *  vacant.
         */
	if (sk->prot->get_port(sk,snum) != 0) return -EADDRINUSE;

	sk->zapped = 0;
	sk->sport = sk->num;    /* we don't really have a short */
	sk->daddr = 0;
	sk->dport = 0;

        /*
         *  Ask the protocol to place this into its hash
         *  list.  In the case of MTP we put the sk address
         *  against the user part in the ss7mtp_cb
         *  associated with the routeset for (sk->saddr).
         */
	sk->prot->hash(sk);

        /*
         *  Add it to the list of SS7 socket in ss7_prot.
         */
	add_to_prot_sklist(sk);
	sk->dst_cache=NULL;
	return(0);
}


/*
 *  Connect the socket tot he address specified.  This is
 *  roughed in only for later SCCP Class 2 and 3 use.  We do
 *  not support connect in the sccp_prot yet.
 */
static int ss7_connect(struct socket *sock, struct sockaddr *uaddr, int addr_len, int flags)
{
    struct sock *sk = sock->sk;
    int err;
    (void)&ss7_connect;

    if (sk->prot->connect == NULL)
        return (-EOPNOTSUPP);
    err = sk->prot->connect(sk, (struct sockaddr *)uaddr, addr_len);
    if (err < 0)
        return err;
    return (0);
}

/*
 *  Accept a pending connection.  This is only useful for
 *  SCCP connection classes 2 and 3 which are not supported
 *  yet.
 */
static int ss7_accept(struct socket *sock, struct socket *newsock, int flags)
{
    struct sock *sk1 = sock->sk, *sk2;
    struct sock *newsk = newsock->sk;
    int err;
    (void)&ss7_accept;

    err = -EOPNOTSUPP;
    if (sk1->prot->connect == NULL)
        return err;
    if ((sk2 = sk1->prot->accept(sk1,flags))==NULL) {
        err = sock_error(sk1);
        return err;
    }
    newsock->sk = sk2;
    sk2->socket = newsock;
    newsk->socket = NULL;
    destroy_sock(newsk);
    newsock->state = SS_CONNECTED;
    return 0;
}



/*
 *  Get the address associated with this socket.  If we get
 *  our name, we will return the SS7 address (Point Code) of
 *  the local size of the connection with is the MTP Point
 *  Code of the protoco to which we were bound.  The remote
 *  side of the connection only makes sense for some MTP
 *  user parse or on a link-by-link basis.
 */
static int ss7_getname(struct socket *sock,
        struct sockaddr *uaddr, int *uaddr_len, int peer)
{
	struct sock *sk          = sock->sk;
	struct sockaddr_ss7 *spc = (struct sockaddr_ss7 *)uaddr;
  
	spc->ss7_family = AF_SS7;
	if (peer) {
                return -ENOTCONN;   /* no SCCP class 2 or 3 support yet */
	} else {
		__u32 addr = sk->rcv_saddr;
		if (!addr) addr = sk->saddr;
		spc->ss7_si = sk->sport;
		spc->ss7_dpc.s_addr = addr;
	}
	*uaddr_len = sizeof(*spc);
	return(0);
}

/*
 *  Receive a message for the socket and deliver it to the
 *  socket user.
 */
static int ss7_recvmsg(struct socket *sock,
        struct msghdr *msg, int size, int flags, struct scm_cookie *scm)
{
	struct sock *sk = sock->sk;
	int addr_len = 0;
	int err;
	
	if (sock->flags & SO_ACCEPTCON) return(-EINVAL);
	if (sk->prot->recvmsg == NULL) return(-EOPNOTSUPP);
        if (!sk->num) return(-EAGAIN);
	err = sk->prot->recvmsg(sk,
                msg, size, flags&MSG_DONTWAIT, flags&~MSG_DONTWAIT, &addr_len);
	if (err >= 0) msg->msg_namelen = addr_len;
	return err;
}

/*
 *  Send a message to the lower protocol layer for ultimate
 *  transmission at the network device.
 */
static int ss7_sendmsg(struct socket *sock, struct msghdr *msg, int size, struct scm_cookie *scm)
{
    struct sock *sk = sock->sk;

    if (sk->shutdown & SEND_SHUTDOWN) {
        if (!(msg->msg_flags&MSG_NOSIGNAL)) send_sig(SIGPIPE, current, 1);
        return(-EPIPE);
    }
    if (sk->prot->sendmsg == NULL) return(-EOPNOTSUPP);
    if (sk->err) return sock_error(sk);
    if (!sk->num) return(-EAGAIN);

    return sk->prot->sendmsg(sk, msg, size);
}

/*
 *  Shut down the socket in one or both directions; however,
 *  neither is really applicable to SS7 MTP, here for SCCP
 *  later.
 */
static int ss7_shutdown(struct socket *sock, int how)
{
	struct sock *sk = sock->sk;
        (void)&ss7_shutdown;

	how++;
	if ((how & ~SHUTDOWN_MASK) || how==0)
		return(-EINVAL);
	if (!sk)
		return(-ENOTCONN);
        return (-ENOTCONN);
#if 0
	if (sock->state == SS_CONNECTING && sk->state == TCP_ESTABLISHED)
		sock->state = SS_CONNECTED;
	if (!tcp_connected(sk->state)) 
		return(-ENOTCONN);
	sk->shutdown |= how;
	if (sk->prot->shutdown) sk->prot->shutdown(sk, how);
	sk->state_change(sk);
	return(0);
#endif
}

/*
 *  Simply chain poll requests on to the protocol layer and
 *  let them deal with them.
 */
static unsigned int ss7_poll(struct file * file, struct socket *sock, poll_table *wait)
{
    (void)file;
    (void)wait;
    return (sock->sk->prot->poll) ?  sock->sk->prot->poll(file, sock, wait) : 0;
}

/*
 *  Device specific ioctls derived from net/ipv4/devinet.c
 */
static int devss7_ioctl(unsigned int cmd, void *arg)
{
	struct ifreq ifr;
	struct sockaddr_ss7 *spc = (struct sockaddr_ss7 *)&ifr.ifr_addr;
        struct ss7_routeset *rs_lcl = NULL;
        struct ss7_routeset *rs_adj = NULL;
        struct ss7_iface *iface;
        struct device *dev;

	/*
         *  Fetch the caller's info block into kernel space
	 */

	if (copy_from_user(&ifr, arg, sizeof(struct ifreq)))
		return -EFAULT;
	ifr.ifr_name[IFNAMSIZ-1] = 0;

#ifdef CONFIG_KMOD
	dev_load(ifr.ifr_name);
#endif
	if ((dev = dev_get(ifr.ifr_name)) == NULL)
            return -ENODEV;
        if ((iface = (struct ss7_iface *)dev->ss7_ptr)==NULL)
            return -ENODEV;

	switch(cmd) {
            case SIOCGIFADDR:	/* Get interface address */
                if (!iface->link.linkset) return -EADDRNOTAVAIL;
                if (!(rs_adj = iface->link.linkset->adjacent)) return -EADDRNOTAVAIL;
                if (!(rs_lcl = iface->link.linkset->local)) return -EADDRNOTAVAIL;

                memset(spc, 0, sizeof(*spc));
                spc->ss7_family = AF_SS7;
                spc->ss7_opc.s_addr = rs_lcl->addr;
                spc->ss7_dpc.s_addr = rs_adj->addr;
                spc->ss7_sls = iface->link.index;

                if (copy_to_user(arg, &ifr, sizeof(struct ifreq)))
                    return -EFAULT;
                return(0);

            case SIOCSIFFLAGS:
                if (!capable(CAP_NET_ADMIN)) return -EACCES;
                return dev_change_flags(dev,ifr.ifr_flags);
	
            case SIOCSIFADDR:	/* Set interface address (and family) */
		if (spc->ss7_family != AF_SS7) return -EINVAL;
                if (!(rs_lcl = ss7_rt_type(spc->ss7_opc, 0, SS7_RST_LOCAL   )))
                    return -EADDRNOTAVAIL;
                if (!(rs_adj = ss7_rt_type(spc->ss7_dpc, 0, SS7_RST_ADJACENT)))
                    return -EADDRNOTAVAIL;
                return ss7_move_link(&iface->link,rs_lcl,rs_adj,spc->ss7_sls);

            default:
                return -EINVAL;
	}
	return 0;
}


/*
 *  ioctl() calls you can issue on an SS7 socket. Process
 *  some, pass some on the the socket.  Many are not
 *  applicatble to SS7.  We define some new ones but reuse
 *  as many of the IP ones as possible.
 *
 *  Note: this function copied straight from
 *  net/ipv4/af_inet.c and modified to meet SS7 needs.
 *
 */
static int ss7_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
{
        struct sock *sk = sock->sk;
        int err;
        int pid;

        switch(cmd) 
        {
                case FIOSETOWN:
                case SIOCSPGRP:
                        err = get_user(pid, (int *) arg);
                        if (err)
                                return err; 
                        if (current->pid != pid && current->pgrp != -pid && 
                            !capable(CAP_NET_ADMIN))
                                return -EPERM;
                        sk->proc = pid;
                        return(0);
                case FIOGETOWN:
                case SIOCGPGRP:
                        return put_user(sk->proc, (int *)arg);
                case SIOCGSTAMP:
                        if(sk->stamp.tv_sec==0)
                                return -ENOENT;
                        err = copy_to_user((void *)arg,&sk->stamp,sizeof(struct timeval));
                        if (err)
                                err = -EFAULT;
                        return err;
                case SIOCADDRT:
                case SIOCDELRT:
                case SIOCRTMSG:
                        /*
                         *  SS7 has a different routing
                         *  table mechanism and a different
                         *  routing table than IP.  We
                         *  redirect this to the right
                         *  place.
                         */
                        return(ss7_route_ioctl(cmd,(void *) arg));
                case SIOCDARP:
                case SIOCGARP:
                case SIOCSARP:
                case SIOCDRARP:
                case SIOCGRARP:
                case SIOCSRARP:
                        /*
                         *  SS7 doesn't do ARP (yet: I'm
                         *  workin' on it).
                         */
                        return -EINVAL;
                case SIOCGIFBRDADDR:
                case SIOCSIFBRDADDR:
                case SIOCGIFNETMASK:
                case SIOCSIFNETMASK:
                        /*
                         *  SS7 doesn't do broadcast or
                         *  netmask at interface level.
                         */
                        return -EINVAL;
                case SIOCGIFADDR:       /* Reuse for local Point Code for links */
                case SIOCSIFADDR:       /* Reuse for local Point Code for links */
                case SIOCGIFDSTADDR:    /* Reuse for remote Point Code for links */
                case SIOCSIFDSTADDR:    /* Reuse for remote Point Code for links */
                case SIOCSIFPFLAGS:     /* Private flags: we will use these */
                case SIOCGIFPFLAGS:     /* Private flags: we will use these */
                case SIOCSIFFLAGS:      /* Interface flags like up and down */
                        /*
                         *  Send them to the right place.
                         */
                        return(devss7_ioctl(cmd,(void *) arg));    /* ss7_dev.c */
                case SIOCGIFBR:
                case SIOCSIFBR:
                case SIOCADDDLCI:
                case SIOCDELDLCI:
                        /*
                         *  SS7 isn't configured for bridges
                         *  or DLCIs  ...yet!
                         */
                        return -EINVAL;
                default:
                        if ((cmd >= SIOCDEVPRIVATE) &&
                            (cmd <= (SIOCDEVPRIVATE + 15)))
                                return(dev_ioctl(cmd,(void *) arg));
                        /* that was the generic dev_ioctl
                         * from core/dev.c */

                        if (sk->prot->ioctl==NULL || (err=sk->prot->ioctl(sk, cmd, arg))==-ENOIOCTLCMD)
                                return(dev_ioctl(cmd,(void *) arg));            
                        /* that was the generic dev_ioctl
                         * from core/dev.c */
                        return err;
        }
        /*NOTREACHED*/
        return(0);
}


/*
 *  These are the protocol operations for socket<->protocol
 *  communication as distinguished from protocol<->protocol
 *  communication and the prototocol<->network interface
 *  communication.  This is the BSD way.  Many stacks in
 *  Linux do not follow this, but I will :)
 *  
 */

static struct proto_ops mtp_ops = {
    PF_SS7,

    sock_no_dup,            /* makse no sense for MTP */
    ss7_release,            /* release the socket */
    ss7_bind,               /* bind the socket to an SS7 address */
    sock_no_connect,        /* makes no sense for MTP */
    sock_no_socketpair,     /* makes no sense for MTP */
    sock_no_accept,         /* makes no sense for MTP */
    ss7_getname,            /* only the local address makes sense for MTP */
    ss7_poll,               /* poll for readable/writable */
    ss7_ioctl,              /* ioctls for MTP not link */
    sock_no_listen,         /* makse no sense for MTP */
    sock_no_shutdown,       /* only both sides makes sense for MTP */
    ss7_setsockopt,         /* O.K, but a lot of standard ones no good */
    ss7_getsockopt,         /* O.K, but a lot of standard ones no good */
    sock_no_fcntl,          /* is it not a file device */
    ss7_sendmsg,            /* how we get out messages */
    ss7_recvmsg             /* how we get out messages */
};

#ifdef NEED_SCCP
/*
 *  I don't have anything defined for SCCP sockets yet, I just thought that
 *  I would rough in the socket code for SCCP while I was in here doing
 *  MTP.
 */
static struct proto_ops sccp_cl_ops {
    PF_SS7,

    sock_no_dup,
    ss7_release,
    ss7_bind,
    sock_no_connect,
    sock_no_socketpair,
    sock_no_accept,
    ss7_getname,
    ss7_poll,
    ss7_ioctl,
    sock_no_listen,
    sock_no_shutdown,
    ss7_setsockopt,
    ss7_getsockopt,
    sock_no_fcntl,
    ss7_sendmsg,
    ss7_recvmsg
};

static struct proto_ops sccp_co_ops {
    PF_SS7,

    sock_no_dup,
    ss7_release,
    ss7_bind,
    ss7_connect,
    sock_no_socketpair,
    ss7_accept,
    ss7_getname,
    ss7_poll,
    ss7_ioctl,
    ss7_listen,
    ss7_shutdown,
    ss7_setsockopt,
    ss7_getsockopt,
    sock_no_fcntl,
    ss7_sendmsg,
    ss7_recvmsg
};

static struct net_proto_family ss7_family_ops = {
    PF_SS7,
    ss7_create
};

#ifdef CONFIG_PROC_FS
/*
 *  I need to put some proc filesystem definitions in here
 *  so that the user can query information.  Some stuff like
 *  netstat_get_info, afss7_get_info, mtp_get_info,
 *  sccp_get_info.
 */

static struct proc_dir_entry proc_ss7_mtp = {
    PROC_NET_SS7_MTP, 3, "mtp",             /* defined in <linux/proc_fs.h> */
    S_IFREG | S_IRUGO, 1, 0, 0,
    0, &proc_net_inode_operations,
    mtp_get_info                            /* defined in "proc.c" */
};

static struct proc_dir_entry proc_ss7_sccp = {
    PROC_NET_SS7_SCCP, 4, "sccp",           /* defined in <linux/proc_fs.h> */
    S_IFREG | S_IRUGO, 1, 0, 0,
    0, &proc_net_inode_operations,
    sccp_get_info                           /* defined in "proc.c" */
};

static struct proc_dir_entry proc_ss7_sockstat = {
    PROC_NET_SS7_SOCKSTAT, 8, "sockstat",   /* defined in <linux/proc_fs.h> */
    S_IFREG | S_IRUGO, 1, 0, 0,
    0, &proc_net_inode_operations,
    afss7_get_info                          /* defined in "proc.c" */
};

static struct proc_dir_entry proc_ss7_netstat = {
    PROC_NET_SS7_NETSTAT, 7, "netstat",     /* defined in <linux/proc_fs.h> */
    S_IFREG | S_IRUGO, 1, 0, 0,
    0, &proc_net_inode_operations,
    netstat_get_info                        /* defined in "proc.c" */
};
#endif /* #ifdef CONFIG_PROC_FS */





/*
 *  A packet is received from the packet scheduler for the
 *  device.  We manage to intercept it here so that we can
 *  send it on to the interface before the hard_start_xmit
 *  to the device driver itself.  That way the interface can
 *  process the L2 state machines and queue packets as
 *  desired.  Returning a (1) from the function will cause
 *  the packet scheduler to requeue the packet.
 *
 *  The packet scheduler does not know about our interface.
 *  It gets its information directly from the struct device
 *  owned by the device driver (which we provide to the
 *  device from the interface.)  If we need to do any
 *  messing around at the receive side, we can do it here.
 *
 *  A packet will never come here for the device if it has
 *  it's tbusy flag set, (unless it is coming from a PACKET
 *  socket (?).
 *
 */
int ss7_pckt_rcv(struct sk_buff * skb, struct device *dev, struct packet_type* this)
{
    return ss7if_l3_output(skb,dev);    /* see ss7_dev.c */
};

static struct packet_type ss7_packet_type =
{
    0,              /* this is htons(ETH_P_SS7) <linux/if_ether.h> */
    0,              /* NULL means for any device */
    ss7_pckt_rcv,   /* funcion for packet handler to invoke */
    NULL,           /* packet private data */
    NULL            /* next pointer, initialize to NULL */
};


#ifdef MODULE
void cleanup_module(void)
{
#ifdef CONFIG_PROC_FS
        proc_net_unregister(PROC_NET_SS7_MTP);      /* defined in <linux/proc_fs.h> */
        proc_net_unregister(PROC_NET_SS7_SCCP);     /* defined in <linux/proc_fs.h> */
        proc_net_unregister(PROC_NET_SS7_SOCKSTAT); /* defined in <linux/proc_fs.h> */
        proc_net_unregister(PROC_NET_SS7_NETSTAT);  /* defined in <linux/proc_fs.h> */
#endif /* #ifdef CONFIG_PROC_FS */

        /* do some groovy cleanup functions, free up stuff
         * */

#ifdef CONFIG_SYSCTL
        ss7_unregister_sysctl();
#endif /* #ifdef CONFIG_SYSCTL */

        unregister_netdevice_notifier(&ss7_netdevice_notifier);

        ss7_packet_type.type = htons(ETH_P_SS7);
        dev_remove_pack(&ss7_packet_type);

        sock_unregister(ss7_family_ops.family);
        ss7_rtab_free();
}

int init_module(void)
#else  /* #ifdef MODULE */
__initfunc(void ss7_proto_init(struct net_proto* pro))
#endif /* #ifdef MODULE */
{
        ss7_rtab_init();
        spin_lock_init(&ss7_ifacelist_lock);
        (void)ss7_ifacelist_lock;

        sock_register(&ss7_family_ops);

        ss7_iface_event_init();
        ss7_packet_type.type = htons(ETH_P_SS7);
        dev_add_pack(&ss7_packet_type);

        register_netdevice_notifier(&ss7_netdev_notifier);

#ifdef CONFIG_SYSCTL
        ss7_register_sysctl();
#endif

#ifdef CONFIG_PROC_FS
        proc_net_register(&proc_ss7);   /* register others on their own time! */
#endif

        printk(KERN_INFO "NET4: SS7 for Linux. $Revision: 0.7.2.5 $ for Linux NET4.0\n");

        /* do some groovy initializations here */

#ifdef CONFIG_PROC_FS
        proc_net_register(&proc_ss7_mtp);       /* defined in "proc.c" */
        proc_net_register(&proc_ss7_sccp);      /* defined in "proc.c" */
        proc_net_register(&proc_ss7_netstat);   /* defined in "proc.c" */
        proc_net_register(&proc_ss7_sockstat);  /* defined in "proc.c" */
#endif

#ifdef MODULE
        return 0;
#endif
}

#endif /* defined CONFIG_SS7 */


