/*
 *	Implements an IPX socket layer.
 *
 *	This code is derived from work by
 *		Ross Biro	: 	Writing the original IP stack
 *		Fred Van Kempen :	Tidying up the TCP/IP
 *
 *	Many thanks go to Keith Baker, Institute For Industrial Information
 *	Technology Ltd, Swansea University for allowing me to work on this
 *	in my own time even though it was in some ways related to commercial
 *	work I am currently employed to do there.
 *
 *	All the material in this file is subject to the Gnu license version 2.
 *	Neither Alan Cox nor the Swansea University Computer Society admit 
 *	liability nor provide warranty for any of this software. This material
 *	is provided as is and at no charge.
 *
 *	Revision 0.21:	Uses the new generic socket option code.
 *	Revision 0.22:	Gcc clean ups and drop out device registration. Use the
 *			new multi-protocol edition of hard_header
 *	Revision 0.23:  IPX /proc by Mark Evans. Adding a route will
 *     			will overwrite any existing route to the same network.
 *	Revision 0.24:	Supports new /proc with no 4K limit
 *	Revision 0.25:	Add ephemeral sockets, passive local network
 *			identification, support for local net 0 and
 *			multiple datalinks <Greg Page>
 *	Revision 0.26:  Device drop kills IPX routes via it. (needed for module)
 *	Revision 0.27:  Autobind <Mark Evans>
 *	Revision 0.28:  Small fix for multiple local networks <Thomas Winder>
 *	Revision 0.29:  Assorted major errors removed <Mark Evans>
 *			Small correction to promisc mode error fix <Alan Cox>
 *			Asynchronous I/O support. Changed to use notifiers
 *			and the newer packet_type stuff. Assorted major
 *			fixes <Alejandro Liu>
 *	Revision 0.30:	Moved to net/ipx/...	<Alan Cox>
 *			Don't set address length on recvfrom that errors.
 *			Incorrect verify_area.
 *	Revision 0.31:	New sk_buffs. This still needs a lot of 
 *			testing. <Alan Cox>
 *	Revision 0.32:  Using sock_alloc_send_skb, firewall hooks. <Alan Cox>
 *			Supports sendmsg/recvmsg
 *	Revision 0.33:	Internal network support, routing changes, uses a
 *			protocol private area for ipx data.
 *	Revision 0.34:	Module support. <Jim Freeman>
 *	Revision 0.35:  Checksum support. <Neil Turton>, hooked in by <Alan Cox>
 *			Handles WIN95 discovery packets <Volker Lendecke>
 *	Revision 0.36:	Internal bump up for 2.1
 *	Revision 0.37:	Began adding POSIXisms.
 *	Revision 0.38:  Asynchronous socket stuff made current.
 *	Revision 0.39:  SPX interfaces
 *	Revision 0.40:  Tiny SIOCGSTAMP fix (chris@cybernet.co.nz)
 *
 *	Protect the module by a MOD_INC_USE_COUNT/MOD_DEC_USE_COUNT
 *	pair. Also, now usage count is managed this way
 *	-Count one if the auto_interface mode is on
 *      -Count one per configured interface
 *
 *	Jacques Gelinas (jacques@solucorp.qc.ca)
 *
 *
 * 	Portions Copyright (c) 1995 Caldera, Inc. <greg@caldera.com>
 *	Neither Greg Page nor Caldera, Inc. admit liability nor provide
 *	warranty for any of this software. This material is provided
 *	"AS-IS" and at no charge.
 */

#include <linux/config.h>
#if defined (CONFIG_IPX) || defined (CONFIG_IPX_MODULE)
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/socket.h>
#include <linux/in.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/string.h>
#include <linux/sockios.h>
#include <linux/net.h>
#include <linux/netdevice.h>
#include <net/ipx.h>
#include <linux/inet.h>
#include <linux/route.h>
#include <net/sock.h>
#include <asm/uaccess.h>
#include <asm/system.h>
#include <linux/fcntl.h>
#include <linux/mm.h>
#include <linux/termios.h>	/* For TIOCOUTQ/INQ */
#include <linux/interrupt.h>
#include <net/p8022.h>
#include <net/p8022tr.h>
#include <net/psnap.h>
#include <linux/proc_fs.h>
#include <linux/stat.h>
#include <linux/firewall.h>
#include <linux/init.h>

#ifdef MODULE
static void ipx_proto_finito(void);
#endif /* def MODULE */

/* Configuration Variables */
static unsigned char	ipxcfg_max_hops = 16;
static char		ipxcfg_auto_select_primary = 0;
static char		ipxcfg_auto_create_interfaces = 0;

/* Global Variables */
static struct datalink_proto	*p8022_datalink 	= NULL;
static struct datalink_proto	*p8022tr_datalink 	= NULL;
static struct datalink_proto	*pEII_datalink 		= NULL;
static struct datalink_proto	*p8023_datalink 	= NULL;
static struct datalink_proto	*pSNAP_datalink 	= NULL;

static struct proto_ops ipx_dgram_ops;

static struct net_proto_family *spx_family_ops;

static ipx_route 	*ipx_routes 		= NULL;
static ipx_interface	*ipx_interfaces 	= NULL;
static ipx_interface	*ipx_primary_net 	= NULL;
static ipx_interface	*ipx_internal_net 	= NULL;

static int ipxcfg_set_auto_create(char val)
{
	if(ipxcfg_auto_create_interfaces != val)
	{
		if(val)
			MOD_INC_USE_COUNT;
		else
			MOD_DEC_USE_COUNT;

		ipxcfg_auto_create_interfaces = val;
	}

	return (0);
}

static int ipxcfg_set_auto_select(char val)
{
	ipxcfg_auto_select_primary = val;
	if(val && (ipx_primary_net == NULL))
		ipx_primary_net = ipx_interfaces;

	return (0);
}

static int ipxcfg_get_config_data(ipx_config_data *arg)
{
	ipx_config_data	vals;

	vals.ipxcfg_auto_create_interfaces = ipxcfg_auto_create_interfaces;
	vals.ipxcfg_auto_select_primary = ipxcfg_auto_select_primary;

	return (copy_to_user(arg, &vals, sizeof(vals)) ? -EFAULT : 0);
}

/**************************************************************************\
*                                                                          *
* Handlers for the socket list.                                            *
*                                                                          *
\**************************************************************************/

/*
 * Note: Sockets may not be removed _during_ an interrupt or inet_bh
 * handler using this technique. They can be added although we do not
 * use this facility.
 */

void ipx_remove_socket(struct sock *sk)
{
	struct sock *s;
	ipx_interface *intrfc;
	unsigned long flags;

	save_flags(flags);
	cli();

	/* Determine interface with which socket is associated */
	intrfc = sk->protinfo.af_ipx.intrfc;
	if(intrfc == NULL)
	{
		restore_flags(flags);
		return;
	}

	s = intrfc->if_sklist;
	if(s == sk)
	{
		intrfc->if_sklist = s->next;
		restore_flags(flags);
		return;
	}

	while(s && s->next)
	{
		if(s->next == sk)
		{
			s->next = sk->next;
			restore_flags(flags);
			return;
		}
		s = s->next;
	}
	restore_flags(flags);
}

/*
 * This is only called from user mode. Thus it protects itself against
 * interrupt users but doesn't worry about being called during work.
 * Once it is removed from the queue no interrupt or bottom half will
 * touch it and we are (fairly 8-) ) safe.
 */
static void ipx_destroy_socket(struct sock *sk)
{
	struct sk_buff	*skb;

	ipx_remove_socket(sk);
	while((skb = skb_dequeue(&sk->receive_queue)) != NULL)
		kfree_skb(skb);

	sk_free(sk);
}

/* 
 * The following code is used to support IPX Interfaces (IPXITF).  An
 * IPX interface is defined by a physical device and a frame type.
 */
static ipx_route * ipxrtr_lookup(__u32);

static void ipxitf_clear_primary_net(void)
{
	if(ipxcfg_auto_select_primary && (ipx_interfaces != NULL))
		ipx_primary_net = ipx_interfaces;
	else
		ipx_primary_net = NULL;
}

static ipx_interface *ipxitf_find_using_phys(struct device *dev, unsigned short datalink)
{
	ipx_interface	*i;

	for(i = ipx_interfaces;
		i && ((i->if_dev != dev) || (i->if_dlink_type != datalink));
		i = i->if_next)
		;

	return (i);
}

static ipx_interface *ipxitf_find_using_net(__u32 net)
{
	ipx_interface	*i;

	if(!net)
		return (ipx_primary_net);

	for(i = ipx_interfaces; i && (i->if_netnum != net); i = i->if_next)
		;

	return (i);
}

/* Sockets are bound to a particular IPX interface. */
static void ipxitf_insert_socket(ipx_interface *intrfc, struct sock *sk)
{
	struct sock *s;

	sk->protinfo.af_ipx.intrfc = intrfc;
	sk->next = NULL;
	if(intrfc->if_sklist == NULL)
		intrfc->if_sklist = sk;
	else
	{
		for (s = intrfc->if_sklist; s->next != NULL; s = s->next)
			;
		s->next = sk;
	}
}

static struct sock *ipxitf_find_socket(ipx_interface *intrfc, unsigned short port)
{
	struct sock *s;

	for(s = intrfc->if_sklist;
		(s != NULL) && (s->protinfo.af_ipx.port != port);
		s = s->next)
		;

	return (s);
}

#ifdef CONFIG_IPX_INTERN

static struct sock *ipxitf_find_internal_socket(ipx_interface *intrfc,
			    unsigned char *node, unsigned short port)
{
	struct sock *s = intrfc->if_sklist;

	while(s != NULL)
	{
		if((s->protinfo.af_ipx.port == port)
		    && (memcmp(node, s->protinfo.af_ipx.node, IPX_NODE_LEN) == 0))
		{
			break;
		}
		s = s->next;
	}

	return (s);
}
#endif

static void ipxrtr_del_routes(ipx_interface *);

static void ipxitf_down(ipx_interface *intrfc)
{
	ipx_interface *i;
	struct sock *s, *t;

	/* Delete all routes associated with this interface */
	ipxrtr_del_routes(intrfc);

	/* error sockets */
	for(s = intrfc->if_sklist; s != NULL; )
	{
		s->err = ENOLINK;
		s->error_report(s);
		s->protinfo.af_ipx.intrfc = NULL;
		s->protinfo.af_ipx.port = 0;
		s->zapped=1;	/* Indicates it is no longer bound */
		t = s;
		s = s->next;
		t->next = NULL;
	}
	intrfc->if_sklist = NULL;

	/* remove this interface from list */
	if(intrfc == ipx_interfaces)
		ipx_interfaces = intrfc->if_next;
	else
	{
		for(i = ipx_interfaces;
			(i != NULL) && (i->if_next != intrfc);
			i = i->if_next)
			;
		if((i != NULL) && (i->if_next == intrfc))
			i->if_next = intrfc->if_next;
	}

	/* remove this interface from *special* networks */
	if(intrfc == ipx_primary_net)
		ipxitf_clear_primary_net();
	if(intrfc == ipx_internal_net)
		ipx_internal_net = NULL;

	kfree_s(intrfc, sizeof(*intrfc));
	MOD_DEC_USE_COUNT;

	return;
}

static int ipxitf_device_event(struct notifier_block *notifier, unsigned long event, void *ptr)
{
	struct device *dev = ptr;
	ipx_interface *i, *tmp;

	if(event != NETDEV_DOWN)
		return NOTIFY_DONE;

	for(i = ipx_interfaces; i != NULL;)
	{
		tmp = i->if_next;
		if(i->if_dev == dev)
			ipxitf_down(i);
		i = tmp;

	}

	return (NOTIFY_DONE);
}

static int ipxitf_def_skb_handler(struct sock *sock, struct sk_buff *skb)
{
	int retval;

	if((retval = sock_queue_rcv_skb(sock, skb)) < 0)
		kfree_skb(skb);

	return (retval);
}

/*
 * On input skb->sk is NULL. Nobody is charged for the memory.
 */

#ifdef CONFIG_IPX_INTERN
static int ipxitf_demux_socket(ipx_interface *intrfc, struct sk_buff *skb, int copy)
{
	struct ipxhdr *ipx = skb->nh.ipxh;
	struct sock *s;

	int is_broadcast = (memcmp(ipx->ipx_dest.node, ipx_broadcast_node,
				   IPX_NODE_LEN) == 0);

	s = intrfc->if_sklist;

	while(s != NULL)
	{
		if((s->protinfo.af_ipx.port == ipx->ipx_dest.sock)
		    && (is_broadcast
			|| (memcmp(ipx->ipx_dest.node, s->protinfo.af_ipx.node,
				   IPX_NODE_LEN) == 0)))
		{
			/* We found a socket to which to send */
			struct sk_buff *skb1;

			if(copy != 0)
			{
				skb1 = skb_clone(skb, GFP_ATOMIC);
				if (skb1 == NULL)
					return -ENOMEM;
			}
			else
			{
				skb1 = skb;
				copy = 1; /* skb may only be used once */
			}
			ipxitf_def_skb_handler(s, skb1);

			/* On an external interface, one socket can listen */
			if(intrfc != ipx_internal_net)
				break;
		}
		s = s->next;
	}

	/* skb was solely for us, and we did not make a copy, so free it. */
	if(copy == 0)
		kfree_skb(skb);

	return (0);
}

#else

static int ipxitf_demux_socket(ipx_interface *intrfc, struct sk_buff *skb, int copy)
{
	struct ipxhdr *ipx = skb->nh.ipxh;
	struct sock *sock1 = NULL, *sock2 = NULL;
	struct sk_buff *skb1 = NULL, *skb2 = NULL;

	sock1 = ipxitf_find_socket(intrfc, ipx->ipx_dest.sock);

	/*
	 * We need to check if there is a primary net and if
	 * this is addressed to one of the *SPECIAL* sockets because
	 * these need to be propagated to the primary net.
	 * The *SPECIAL* socket list contains: 0x452(SAP), 0x453(RIP) and
	 * 0x456(Diagnostic).
	 */

	if(ipx_primary_net && (intrfc != ipx_primary_net))
	{
		switch(ntohs(ipx->ipx_dest.sock))
		{
			case 0x452:
			case 0x453:
			case 0x456:
				/*
				 * The appropriate thing to do here is to
				 * dup the packet and route to the primary net
				 * interface via ipxitf_send; however, we'll 
				 * cheat and just demux it here.
				 */
				sock2 = ipxitf_find_socket(ipx_primary_net,
					ipx->ipx_dest.sock);
				break;
			default:
				break;
		}
	}

	/*
	 * If there is nothing to do return. The kfree will cancel any charging.
	 */
	if(sock1 == NULL && sock2 == NULL)
	{
		if(!copy)
			kfree_skb(skb);
		return (0);
	}

	/*
	 * This next segment of code is a little awkward, but it sets it up
	 * so that the appropriate number of copies of the SKB are made and
	 * that skb1 and skb2 point to it (them) so that it (they) can be
	 * demuxed to sock1 and/or sock2.  If we are unable to make enough
	 * copies, we do as much as is possible.
	 */

	if(copy)
		skb1 = skb_clone(skb, GFP_ATOMIC);
	else
		skb1 = skb;

	if(skb1 == NULL)
		return (-ENOMEM);

	/* Do we need 2 SKBs? */
	if(sock1 && sock2)
		skb2 = skb_clone(skb1, GFP_ATOMIC);
	else
		skb2 = skb1;

	if(sock1)
		(void) ipxitf_def_skb_handler(sock1, skb1);

	if(skb2 == NULL)
		return (-ENOMEM);

	if(sock2)
		(void) ipxitf_def_skb_handler(sock2, skb2);

	return (0);
}
#endif	/* CONFIG_IPX_INTERN */

static struct sk_buff *ipxitf_adjust_skbuff(ipx_interface *intrfc, struct sk_buff *skb)
{
	struct sk_buff *skb2;
	int in_offset = skb->h.raw - skb->head;
	int out_offset = intrfc->if_ipx_offset;
	int len;

	/* Hopefully, most cases */
	if(in_offset >= out_offset)
		return (skb);

	/* Need new SKB */
	len  = skb->len + out_offset;
	skb2 = alloc_skb(len, GFP_ATOMIC);
	if(skb2 != NULL)
	{
		skb_reserve(skb2, out_offset);
		skb2->nh.raw =
		skb2->h.raw = skb_put(skb2,skb->len);
		memcpy(skb2->h.raw, skb->h.raw, skb->len);
	}
	kfree_skb(skb);

	return (NULL);
}

static int ipxitf_send(ipx_interface *intrfc, struct sk_buff *skb, char *node)
{
	struct ipxhdr *ipx = skb->nh.ipxh;
	struct device *dev = intrfc->if_dev;
	struct datalink_proto *dl = intrfc->if_dlink;
	char dest_node[IPX_NODE_LEN];
	int send_to_wire = 1;
	int addr_len;
	
	/* 
	 * We need to know how many skbuffs it will take to send out this
	 * packet to avoid unnecessary copies.
	 */
	 
	if((dl == NULL) || (dev == NULL) || (dev->flags & IFF_LOOPBACK)) 
		send_to_wire = 0;	/* No non looped */

	/*
	 * See if this should be demuxed to sockets on this interface 
	 *
	 * We want to ensure the original was eaten or that we only use
	 * up clones.
	 */
	 
	if(ipx->ipx_dest.net == intrfc->if_netnum) 
	{
		/*
		 * To our own node, loop and free the original.
		 * The internal net will receive on all node address.
		 */
		if((intrfc == ipx_internal_net)
		    || memcmp(intrfc->if_node, node, IPX_NODE_LEN) == 0) 
		{
			/* Don't charge sender */
			skb_orphan(skb);

			/* Will charge receiver */
			return (ipxitf_demux_socket(intrfc, skb, 0));
		}

		/* Broadcast, loop and possibly keep to send on. */
		if(memcmp(ipx_broadcast_node, node, IPX_NODE_LEN) == 0)
		{
			if(!send_to_wire)
				skb_orphan(skb);
			ipxitf_demux_socket(intrfc, skb, send_to_wire);
			if(!send_to_wire)
				return (0);
		}
	}

	/*
	 * If the originating net is not equal to our net; this is routed
	 * We are still charging the sender. Which is right - the driver
	 * free will handle this fairly.
	 */
	if(ipx->ipx_source.net != intrfc->if_netnum)
	{
		/*
		 * Unshare the buffer before modifying the count in
		 * case its a flood or tcpdump
		 */
		skb = skb_unshare(skb, GFP_ATOMIC);
		if(!skb)
			return (0);
		if(++(ipx->ipx_tctrl) > ipxcfg_max_hops)
			send_to_wire = 0;
	}

	if(!send_to_wire)
	{
		kfree_skb(skb);
		return (0);
	}

	/* Determine the appropriate hardware address */
	addr_len = dev->addr_len;
	if(memcmp(ipx_broadcast_node, node, IPX_NODE_LEN) == 0)
		memcpy(dest_node, dev->broadcast, addr_len);
	else
		memcpy(dest_node, &(node[IPX_NODE_LEN-addr_len]), addr_len);

	/* Make any compensation for differing physical/data link size */
	skb = ipxitf_adjust_skbuff(intrfc, skb);
	if(skb == NULL)
		return (0);

	/* set up data link and physical headers */
	skb->dev = dev;
	skb->protocol = htons(ETH_P_IPX);
	dl->datalink_header(dl, skb, dest_node);

	/* Send it out */
	dev_queue_xmit(skb);

	return (0);
}

static int ipxrtr_add_route(__u32, ipx_interface *, unsigned char *);

static int ipxitf_add_local_route(ipx_interface *intrfc)
{
	return (ipxrtr_add_route(intrfc->if_netnum, intrfc, NULL));
}

static const char * ipx_frame_name(unsigned short);
static const char * ipx_device_name(ipx_interface *);

static int ipxitf_rcv(ipx_interface *intrfc, struct sk_buff *skb)
{
	struct ipxhdr	*ipx = skb->nh.ipxh;
	ipx_interface	*i;

	/* We firewall first, ask questions later. */
	if (call_in_firewall(PF_IPX, skb->dev, ipx, NULL, &skb)!=FW_ACCEPT)
	{
		kfree_skb(skb);
		return (0);
	}

	/* See if we should update our network number */
	if(!intrfc->if_netnum  /* net number of intrfc not known yet (== 0) */
		&& (ipx->ipx_source.net == ipx->ipx_dest.net) /* intra packet */
		&& ipx->ipx_source.net)  /* source net number of packet != 0 */
	{
		/* NB: NetWare servers lie about their hop count so we
		 * dropped the test based on it.  This is the best way
		 * to determine this is a 0 hop count packet.
		 */
		if((i=ipxitf_find_using_net(ipx->ipx_source.net)) == NULL)
		{
			intrfc->if_netnum = ipx->ipx_source.net;
			(void) ipxitf_add_local_route(intrfc);
		}
		else
		{
			printk(KERN_WARNING "IPX: Network number collision %lx\n        %s %s and %s %s\n",
				(long unsigned int) htonl(ipx->ipx_source.net),
				ipx_device_name(i),
				ipx_frame_name(i->if_dlink_type),
				ipx_device_name(intrfc),
				ipx_frame_name(intrfc->if_dlink_type));
		}
	}

	if(ipx->ipx_type == IPX_TYPE_PPROP
		&& ipx->ipx_tctrl < 8 
		&& skb->pkt_type != PACKET_OTHERHOST) 
	{
		int i;
        	ipx_interface *ifcs;
		struct sk_buff *skb2;
  		__u32 *l;
		char *c;
		
		c = (char *) skb->data;
		c += sizeof(struct ipxhdr);
		l = (__u32 *) c;

		i = 0;

		/* Dump packet if too many hops or already seen this net */
		if(ipx->ipx_tctrl < 8)
			for( ; i < ipx->ipx_tctrl; i++)
				if(*l++ == intrfc->if_netnum)
					break;

		if(i == ipx->ipx_tctrl) 
		{ 
			/* < 8 hops && input itfc not in list */
			*l = intrfc->if_netnum; /* insert recvd netnum into list */
			/* xmit on all other interfaces... */
			for(ifcs = ipx_interfaces; ifcs != NULL; ifcs = ifcs->if_next) 
			{
				/* That aren't in the list */
				l = (__u32 *) c;
				for(i = 0; i <= ipx->ipx_tctrl; i++)
					if(ifcs->if_netnum == *l++)
						break;
				if(i - 1 == ipx->ipx_tctrl) 
				{
					ipx->ipx_dest.net = ifcs->if_netnum;
					/* See if we are allowed to firewall forward */
					if(call_fw_firewall(PF_IPX, skb->dev, ipx, NULL, &skb) == FW_ACCEPT)
					{
					        skb2=skb_clone(skb, GFP_ATOMIC);
						ipxrtr_route_skb(skb2);
					}
				}
			}

			/* Reset network number in packet */
			ipx->ipx_dest.net = intrfc->if_netnum;
		}
	}

	if(!ipx->ipx_dest.net)
		ipx->ipx_dest.net = intrfc->if_netnum;
	if(!ipx->ipx_source.net)
		ipx->ipx_source.net = intrfc->if_netnum;

	if(intrfc->if_netnum != ipx->ipx_dest.net)
	{
		/* See if we are allowed to firewall forward */
		if(call_fw_firewall(PF_IPX, skb->dev, ipx, NULL, &skb) != FW_ACCEPT)
		{
			kfree_skb(skb);
			return (0);
		}

		/* We only route point-to-point packets. */
		if(skb->pkt_type == PACKET_HOST)
		{
			skb=skb_unshare(skb, GFP_ATOMIC);
			if(skb)
				return (ipxrtr_route_skb(skb));
			else
				return (0);
		}

		kfree_skb(skb);
		return (0);
	}

	/* see if we should keep it */
	if((memcmp(ipx_broadcast_node, ipx->ipx_dest.node, IPX_NODE_LEN) == 0)
		|| (memcmp(intrfc->if_node, ipx->ipx_dest.node, IPX_NODE_LEN) == 0))
	{
		return (ipxitf_demux_socket(intrfc, skb, 0));
	}

	/* we couldn't pawn it off so unload it */
	kfree_skb(skb);
	return (0);
}

static void ipxitf_insert(ipx_interface *intrfc)
{
	ipx_interface *i;

	intrfc->if_next = NULL;
	if(ipx_interfaces == NULL)
		ipx_interfaces = intrfc;
	else
	{
		for(i = ipx_interfaces; i->if_next != NULL; i = i->if_next)
			;
		i->if_next = intrfc;
	}

	if(ipxcfg_auto_select_primary && (ipx_primary_net == NULL))
		ipx_primary_net = intrfc;

	MOD_INC_USE_COUNT;

	return;
}

static int ipxitf_create_internal(ipx_interface_definition *idef)
{
	ipx_interface *intrfc;

	/* Only one primary network allowed */
	if(ipx_primary_net != NULL)
		return (-EEXIST);

	/* Must have a valid network number */
	if(!idef->ipx_network)
		return (-EADDRNOTAVAIL);
	if(ipxitf_find_using_net(idef->ipx_network) != NULL)
		return (-EADDRINUSE);

	intrfc = (ipx_interface *)kmalloc(sizeof(ipx_interface),GFP_ATOMIC);
	if(intrfc == NULL)
		return (-EAGAIN);
	intrfc->if_dev		= NULL;
	intrfc->if_netnum	= idef->ipx_network;
	intrfc->if_dlink_type 	= 0;
	intrfc->if_dlink 	= NULL;
	intrfc->if_sklist 	= NULL;
	intrfc->if_internal 	= 1;
	intrfc->if_ipx_offset 	= 0;
	intrfc->if_sknum 	= IPX_MIN_EPHEMERAL_SOCKET;
	memcpy((char *)&(intrfc->if_node), idef->ipx_node, IPX_NODE_LEN);
	ipx_internal_net 	= intrfc;
	ipx_primary_net 	= intrfc;
	ipxitf_insert(intrfc);

	return (ipxitf_add_local_route(intrfc));
}

static int ipx_map_frame_type(unsigned char type)
{
	switch(type) 
	{
		case IPX_FRAME_ETHERII:
			return (htons(ETH_P_IPX));

		case IPX_FRAME_8022:
			return (htons(ETH_P_802_2));

		case IPX_FRAME_TR_8022:
			return (htons(ETH_P_TR_802_2));

		case IPX_FRAME_SNAP:
			return (htons(ETH_P_SNAP));

		case IPX_FRAME_8023:
			return (htons(ETH_P_802_3));
	}

	return (0);
}

static int ipxitf_create(ipx_interface_definition *idef)
{
	struct device *dev;
	unsigned short dlink_type = 0;
	struct datalink_proto *datalink = NULL;
	ipx_interface *intrfc;

	if(idef->ipx_special == IPX_INTERNAL)
		return (ipxitf_create_internal(idef));

	if((idef->ipx_special == IPX_PRIMARY) && (ipx_primary_net != NULL))
		return (-EEXIST);

	if(idef->ipx_network
		&& (ipxitf_find_using_net(idef->ipx_network) != NULL))
		return (-EADDRINUSE);

	switch(idef->ipx_dlink_type) 
	{
		case IPX_FRAME_ETHERII:
			dlink_type 	= htons(ETH_P_IPX);
			datalink 	= pEII_datalink;
			break;

		case IPX_FRAME_TR_8022:
			dlink_type 	= htons(ETH_P_TR_802_2);
			datalink 	= p8022tr_datalink;
			break;

		case IPX_FRAME_8022:
			dlink_type 	= htons(ETH_P_802_2);
			datalink 	= p8022_datalink;
			break;

		case IPX_FRAME_SNAP:
			dlink_type 	= htons(ETH_P_SNAP);
			datalink 	= pSNAP_datalink;
			break;

		case IPX_FRAME_8023:
			dlink_type 	= htons(ETH_P_802_3);
			datalink 	= p8023_datalink;
			break;

		case IPX_FRAME_NONE:
		default:
			break;
	}

	if(datalink == NULL)
		return (-EPROTONOSUPPORT);

	dev = dev_get(idef->ipx_device);
	if(dev == NULL)
		return (-ENODEV);

	if(!(dev->flags & IFF_UP))
		return (-ENETDOWN);

	/* Check addresses are suitable */
	if(dev->addr_len > IPX_NODE_LEN)
		return (-EINVAL);

	if((intrfc = ipxitf_find_using_phys(dev, dlink_type)) == NULL)
	{
		/* Ok now create */
		intrfc = (ipx_interface *)kmalloc(sizeof(ipx_interface),GFP_ATOMIC);
		if(intrfc == NULL)
			return (-EAGAIN);
		intrfc->if_dev		= dev;
		intrfc->if_netnum 	= idef->ipx_network;
		intrfc->if_dlink_type 	= dlink_type;
		intrfc->if_dlink 	= datalink;
		intrfc->if_sklist 	= NULL;
		intrfc->if_sknum 	= IPX_MIN_EPHEMERAL_SOCKET;
		/* Setup primary if necessary */
		if((idef->ipx_special == IPX_PRIMARY))
			ipx_primary_net = intrfc;
		intrfc->if_internal 	= 0;
		intrfc->if_ipx_offset 	= dev->hard_header_len + datalink->header_length;
		if(memcmp(idef->ipx_node, "\000\000\000\000\000\000", IPX_NODE_LEN) == 0)
		{
			memset(intrfc->if_node, 0, IPX_NODE_LEN);
			memcpy((char *)&(intrfc->if_node[IPX_NODE_LEN-dev->addr_len]),
				dev->dev_addr, dev->addr_len);
		}
		else
			memcpy(intrfc->if_node, idef->ipx_node, IPX_NODE_LEN);
		ipxitf_insert(intrfc);
	}

	/* If the network number is known, add a route */
	if(!intrfc->if_netnum)
		return (0);

	return (ipxitf_add_local_route(intrfc));
}

static int ipxitf_delete(ipx_interface_definition *idef)
{
	struct device *dev = NULL;
	unsigned short dlink_type = 0;
	ipx_interface *intrfc;

	if(idef->ipx_special == IPX_INTERNAL) 
	{
		if(ipx_internal_net != NULL) 
		{
			ipxitf_down(ipx_internal_net);
			return (0);
		}
		return (-ENOENT);
	}

	dlink_type = ipx_map_frame_type(idef->ipx_dlink_type);
	if(dlink_type == 0)
		return (-EPROTONOSUPPORT);

	dev = dev_get(idef->ipx_device);
	if(dev == NULL)
		return (-ENODEV);

	intrfc = ipxitf_find_using_phys(dev, dlink_type);
	if(intrfc != NULL)
	{
		ipxitf_down(intrfc);
		return (0);
	}

	return (-EINVAL);
}

static ipx_interface *ipxitf_auto_create(struct device *dev, 
	unsigned short dlink_type)
{
	struct datalink_proto *datalink = NULL;
	ipx_interface *intrfc;

	switch(htons(dlink_type)) 
	{
		case ETH_P_IPX:
			datalink = pEII_datalink;
			break;

		case ETH_P_802_2:
			datalink = p8022_datalink;
			break;

		case ETH_P_TR_802_2:
			datalink = p8022tr_datalink;
			break;

		case ETH_P_SNAP:
			datalink = pSNAP_datalink;
			break;

		case ETH_P_802_3:
			datalink = p8023_datalink;
			break;

		default:
			return (NULL);
	}

	if(dev == NULL)
		return (NULL);

	/* Check addresses are suitable */
	if(dev->addr_len>IPX_NODE_LEN)
		return (NULL);

	intrfc = (ipx_interface *)kmalloc(sizeof(ipx_interface),GFP_ATOMIC);
	if(intrfc != NULL) 
	{
		intrfc->if_dev		= dev;
		intrfc->if_netnum	= 0;
		intrfc->if_dlink_type 	= dlink_type;
		intrfc->if_dlink 	= datalink;
		intrfc->if_sklist 	= NULL;
		intrfc->if_internal 	= 0;
		intrfc->if_sknum 	= IPX_MIN_EPHEMERAL_SOCKET;
		intrfc->if_ipx_offset 	= dev->hard_header_len +
						datalink->header_length;
		memset(intrfc->if_node, 0, IPX_NODE_LEN);
		memcpy((char *)&(intrfc->if_node[IPX_NODE_LEN-dev->addr_len]),
			dev->dev_addr, dev->addr_len);
		ipxitf_insert(intrfc);
	}

	return (intrfc);
}

static int ipxitf_ioctl(unsigned int cmd, void *arg)
{
	struct ifreq ifr;
	int err, val;

	switch(cmd)
	{
		case SIOCSIFADDR: 
		{
			struct sockaddr_ipx *sipx;
			ipx_interface_definition f;

			if(copy_from_user(&ifr, arg, sizeof(ifr)))
				return (-EFAULT);

			sipx = (struct sockaddr_ipx *)&ifr.ifr_addr;
			if(sipx->sipx_family != AF_IPX)
				return (-EINVAL);

			f.ipx_network = sipx->sipx_network;
			memcpy(f.ipx_device,ifr.ifr_name,sizeof(f.ipx_device));
			memcpy(f.ipx_node, sipx->sipx_node, IPX_NODE_LEN);
			f.ipx_dlink_type = sipx->sipx_type;
			f.ipx_special = sipx->sipx_special;

			if(sipx->sipx_action == IPX_DLTITF)
				return (ipxitf_delete(&f));
			else
				return (ipxitf_create(&f));
		}

		case SIOCGIFADDR: 
		{
			struct sockaddr_ipx *sipx;
			ipx_interface *ipxif;
			struct device *dev;

			if(copy_from_user(&ifr, arg, sizeof(ifr)))
				return (-EFAULT);

			sipx = (struct sockaddr_ipx *)&ifr.ifr_addr;
			dev = dev_get(ifr.ifr_name);
			if(!dev)
				return (-ENODEV);

			ipxif = ipxitf_find_using_phys(dev, ipx_map_frame_type(sipx->sipx_type));
			if(ipxif == NULL)
				return (-EADDRNOTAVAIL);

			sipx->sipx_family	= AF_IPX;
			sipx->sipx_network	= ipxif->if_netnum;
			memcpy(sipx->sipx_node, ipxif->if_node, sizeof(sipx->sipx_node));
			err = -EFAULT;
			if(!copy_to_user(arg, &ifr, sizeof(ifr)))
				err = 0;

			return (err);
		}

		case SIOCAIPXITFCRT: 
		{
			err = get_user(val, (unsigned char *) arg);
			if(err)
				return (err);

			return (ipxcfg_set_auto_create(val));
		}

		case SIOCAIPXPRISLT: 
		{
			err = get_user(val, (unsigned char *) arg);
			if(err)
				return (err);

			return (ipxcfg_set_auto_select(val));
		}

		default:
			return (-EINVAL);
	}
}

/**************************************************************************\
*                                                                          *
* Routing tables for the IPX socket layer.                                 *
*                                                                          *
\**************************************************************************/

static ipx_route *ipxrtr_lookup(__u32 net)
{
	ipx_route *r;

	for(r = ipx_routes; (r != NULL) && (r->ir_net != net); r = r->ir_next)
		;

	return (r);
}

static int ipxrtr_add_route(__u32 network, ipx_interface *intrfc, unsigned char *node)
{
	ipx_route *rt;

	/* Get a route structure; either existing or create */
	rt = ipxrtr_lookup(network);
	if(rt == NULL) 
	{
		rt = (ipx_route *)kmalloc(sizeof(ipx_route),GFP_ATOMIC);
		if(rt == NULL)
			return (-EAGAIN);
		rt->ir_next	= ipx_routes;
		ipx_routes	= rt;
	}
	else if(intrfc == ipx_internal_net)
		return (-EEXIST);

	rt->ir_net 	= network;
	rt->ir_intrfc 	= intrfc;
	if(node == NULL)
	{
		memset(rt->ir_router_node, '\0', IPX_NODE_LEN);
		rt->ir_routed = 0;
	}
	else
	{
		memcpy(rt->ir_router_node, node, IPX_NODE_LEN);
		rt->ir_routed = 1;
	}

	return (0);
}

static void ipxrtr_del_routes(ipx_interface *intrfc)
{
	ipx_route **r, *tmp;

	for(r = &ipx_routes; (tmp = *r) != NULL;)
	{
		if(tmp->ir_intrfc == intrfc)
		{
			*r = tmp->ir_next;
			kfree_s(tmp, sizeof(ipx_route));
		}
		else
			r = &(tmp->ir_next);
	}
}

static int ipxrtr_create(ipx_route_definition *rd)
{
	ipx_interface *intrfc;

	/* Find the appropriate interface */
	intrfc = ipxitf_find_using_net(rd->ipx_router_network);
	if(intrfc == NULL)
		return (-ENETUNREACH);

	return (ipxrtr_add_route(rd->ipx_network, intrfc, rd->ipx_router_node));
}

static int ipxrtr_delete(long net)
{
	ipx_route **r;
	ipx_route *tmp;

	for(r = &ipx_routes; (tmp = *r) != NULL;) 
	{
		if(tmp->ir_net == net) 
		{
			/* Directly connected; can't lose route */
			if(!(tmp->ir_routed))
				return (-EPERM);

			*r = tmp->ir_next;
			kfree_s(tmp, sizeof(ipx_route));
			return (0);
		}

		r = &(tmp->ir_next);
	}

	return (-ENOENT);
}

/*
 *	Checksum routine for IPX
 */
 
/* Note: We assume ipx_tctrl==0 and htons(length)==ipx_pktsize */

static __u16 ipx_set_checksum(struct ipxhdr *packet,int length) 
{
	/* 
	 *	NOTE: sum is a net byte order quantity, which optimizes the 
	 *	loop. This only works on big and little endian machines. (I
	 *	don't know of a machine that isn't.)
	 */

	__u32 sum = 0;

	/* Pointer to second word - We skip the checksum field */
	__u16 *p = (__u16 *)&packet->ipx_pktsize;

	/* Number of complete words */
	__u32 i = length >> 1;
	char hops = packet->ipx_tctrl;

	/* Hop count excluded from checksum calc */
	packet->ipx_tctrl = 0;

	/* Loop through all complete words except the checksum field */
	while(--i)
		sum += *p++;

	/* Add on the last part word if it exists */
	if(packet->ipx_pktsize & htons(1))
		sum += ntohs(0xff00) & *p;

	packet->ipx_tctrl = hops;

	/* Do final fixup */
	sum = (sum & 0xffff) + (sum >> 16);

	/* It's a pity there's no concept of carry in C */
	if(sum >= 0x10000)
		sum++;

	return (~sum);
}

/*
 * Route an outgoing frame from a socket.
 */
static int ipxrtr_route_packet(struct sock *sk, struct sockaddr_ipx *usipx, struct iovec *iov, int len, int noblock)
{
	struct sk_buff *skb;
	ipx_interface *intrfc;
	struct ipxhdr *ipx;
	int size;
	int ipx_offset;
	ipx_route *rt = NULL;
	int err;

	/* Find the appropriate interface on which to send packet */
	if(!usipx->sipx_network && (ipx_primary_net != NULL))
	{
		usipx->sipx_network = ipx_primary_net->if_netnum;
		intrfc = ipx_primary_net;
	}
	else
	{
		rt = ipxrtr_lookup(usipx->sipx_network);
		if(rt == NULL)
			return (-ENETUNREACH);

		intrfc = rt->ir_intrfc;
	}

	ipx_offset = intrfc->if_ipx_offset;
	size	= sizeof(struct ipxhdr) + len;
	size 	+= ipx_offset;

	skb = sock_alloc_send_skb(sk, size, 0, noblock, &err);
	if(skb == NULL)
		return (err);

	skb_reserve(skb,ipx_offset);
	skb->sk = sk;

	/* Fill in IPX header */
	ipx = (struct ipxhdr *)skb_put(skb, sizeof(struct ipxhdr));
	ipx->ipx_pktsize= htons(len + sizeof(struct ipxhdr));
	ipx->ipx_tctrl 	= 0;
	ipx->ipx_type 	= usipx->sipx_type;
	skb->h.raw 	= (void *)skb->nh.ipxh = ipx;

	ipx->ipx_source.net = sk->protinfo.af_ipx.intrfc->if_netnum;

#ifdef CONFIG_IPX_INTERN
	memcpy(ipx->ipx_source.node, sk->protinfo.af_ipx.node, IPX_NODE_LEN);
#else
	if((err = ntohs(sk->protinfo.af_ipx.port)) == 0x453 || err == 0x452)
	{
		/* RIP/SAP special handling for mars_nwe */
		ipx->ipx_source.net = intrfc->if_netnum;
		memcpy(ipx->ipx_source.node, intrfc->if_node, IPX_NODE_LEN);
	}
	else
	{
		ipx->ipx_source.net = sk->protinfo.af_ipx.intrfc->if_netnum;
		memcpy(ipx->ipx_source.node, sk->protinfo.af_ipx.intrfc->if_node, IPX_NODE_LEN);
	}
#endif	/* CONFIG_IPX_INTERN */

	ipx->ipx_source.sock 	= sk->protinfo.af_ipx.port;
	ipx->ipx_dest.net	= usipx->sipx_network;
	memcpy(ipx->ipx_dest.node,usipx->sipx_node,IPX_NODE_LEN);
	ipx->ipx_dest.sock	= usipx->sipx_port;

	err = memcpy_fromiovec(skb_put(skb,len),iov,len);
	if(err)
	{
		kfree_skb(skb);
		return (-EFAULT);
	}	

	/* Apply checksum. Not allowed on 802.3 links. */
	if(sk->no_check || intrfc->if_dlink_type == IPX_FRAME_8023)
		ipx->ipx_checksum=0xFFFF;
	else
		ipx->ipx_checksum = ipx_set_checksum(ipx, len + sizeof(struct ipxhdr));

	if(call_out_firewall(PF_IPX, skb->dev, ipx, NULL, &skb) != FW_ACCEPT)
	{
		kfree_skb(skb);
		return (-EPERM);
	}
	
	return (ipxitf_send(intrfc, skb, (rt && rt->ir_routed) ? 
				rt->ir_router_node : ipx->ipx_dest.node));
}
	
int ipxrtr_route_skb(struct sk_buff *skb)
{
	struct ipxhdr *ipx = skb->nh.ipxh;
	ipx_interface *i;
	ipx_route *r;

	r = ipxrtr_lookup(ipx->ipx_dest.net);
	if(r == NULL)	/* no known route */
	{
		kfree_skb(skb);
		return (0);
	}

	i = r->ir_intrfc;
	(void)ipxitf_send(i, skb, (r->ir_routed) ?
			r->ir_router_node : ipx->ipx_dest.node);

	return (0);
}

/*
 * We use a normal struct rtentry for route handling
 */
static int ipxrtr_ioctl(unsigned int cmd, void *arg)
{
	struct rtentry rt;	/* Use these to behave like 'other' stacks */
	struct sockaddr_ipx *sg,*st;
	int err;

	err = copy_from_user(&rt,arg,sizeof(rt));
	if(err)
		return (-EFAULT);

	sg = (struct sockaddr_ipx *)&rt.rt_gateway;
	st = (struct sockaddr_ipx *)&rt.rt_dst;

	if(!(rt.rt_flags & RTF_GATEWAY))
		return (-EINVAL);	/* Direct routes are fixed */
	if(sg->sipx_family != AF_IPX)
		return (-EINVAL);
	if(st->sipx_family != AF_IPX)
		return (-EINVAL);

	switch(cmd)
	{
		case SIOCDELRT:
			return (ipxrtr_delete(st->sipx_network));

		case SIOCADDRT:
		{
			struct ipx_route_definition f;
			f.ipx_network=st->sipx_network;
			f.ipx_router_network=sg->sipx_network;
			memcpy(f.ipx_router_node, sg->sipx_node, IPX_NODE_LEN);
			return (ipxrtr_create(&f));
		}

		default:
			return (-EINVAL);
	}
}

static const char *ipx_frame_name(unsigned short frame)
{
	switch(ntohs(frame)) 
	{
		case ETH_P_IPX:
			return ("EtherII");

		case ETH_P_802_2:
			return ("802.2");

		case ETH_P_SNAP:
			return ("SNAP");

		case ETH_P_802_3:
			return ("802.3");

		case ETH_P_TR_802_2:
			return ("802.2TR");

		default:
			return ("None");
	}
}

static const char *ipx_device_name(ipx_interface *intrfc)
{
	return (intrfc->if_internal ? "Internal" :
		(intrfc->if_dev ? intrfc->if_dev->name : "Unknown"));
}

/* Called from proc fs */
static int ipx_interface_get_info(char *buffer, char **start, off_t offset,
				  int length, int dummy)
{
	ipx_interface *i;
	off_t begin = 0, pos = 0;
	int len = 0;

	/* Theory.. Keep printing in the same place until we pass offset */

	len += sprintf(buffer,"%-11s%-15s%-9s%-11s%s\n", "Network",
		"Node_Address", "Primary", "Device", "Frame_Type");
	for(i = ipx_interfaces; i != NULL; i = i->if_next)
	{
		len += sprintf(buffer+len, "%08lX   ", (long unsigned int)ntohl(i->if_netnum));
		len += sprintf(buffer+len,"%02X%02X%02X%02X%02X%02X   ",
				i->if_node[0], i->if_node[1], i->if_node[2],
				i->if_node[3], i->if_node[4], i->if_node[5]);
		len += sprintf(buffer+len, "%-9s", (i == ipx_primary_net) ?
			"Yes" : "No");
		len += sprintf(buffer+len, "%-11s", ipx_device_name(i));
		len += sprintf(buffer+len, "%s\n",
			ipx_frame_name(i->if_dlink_type));

		/* Are we still dumping unwanted data then discard the record */
		pos = begin + len;

		if(pos < offset)
		{
			len   = 0;	/* Keep dumping into the buffer start */
			begin = pos;
		}
		if(pos > offset + length)	/* We have dumped enough */
			break;
	}

	/* The data in question runs from begin to begin+len */
	*start = buffer + (offset - begin);	/* Start of wanted data */
	len -= (offset - begin); /* Remove unwanted header data from length */
	if(len > length)
		len = length;	/* Remove unwanted tail data from length */

	return (len);
}

static int ipx_get_info(char *buffer, char **start, off_t offset,
			int length, int dummy)
{
	struct sock *s;
	ipx_interface *i;
	off_t begin = 0, pos = 0;
	int len = 0;

	/* Theory.. Keep printing in the same place until we pass offset */

#ifdef CONFIG_IPX_INTERN
	len += sprintf(buffer,"%-28s%-28s%-10s%-10s%-7s%s\n", "Local_Address",
#else
	len += sprintf(buffer,"%-15s%-28s%-10s%-10s%-7s%s\n", "Local_Address",
#endif	/* CONFIG_IPX_INTERN */
			"Remote_Address", "Tx_Queue", "Rx_Queue",
			"State", "Uid");

	for(i = ipx_interfaces; i != NULL; i = i->if_next)
	{
		for(s = i->if_sklist; s != NULL; s = s->next)
		{
#ifdef CONFIG_IPX_INTERN
			len += sprintf(buffer+len,
				       "%08lX:%02X%02X%02X%02X%02X%02X:%04X  ",
                                       (long unsigned int) htonl(s->protinfo.af_ipx.intrfc->if_netnum),
				       s->protinfo.af_ipx.node[0],
				       s->protinfo.af_ipx.node[1],
				       s->protinfo.af_ipx.node[2],
				       s->protinfo.af_ipx.node[3],
				       s->protinfo.af_ipx.node[4],
				       s->protinfo.af_ipx.node[5],
				       htons(s->protinfo.af_ipx.port));
#else
			len += sprintf(buffer+len,"%08lX:%04X  ",
				       htonl(i->if_netnum),
				       htons(s->protinfo.af_ipx.port));
#endif	/* CONFIG_IPX_INTERN */

			if(s->state != TCP_ESTABLISHED)
				len += sprintf(buffer+len, "%-28s", "Not_Connected");
			else
			{
				len += sprintf(buffer+len,
					"%08lX:%02X%02X%02X%02X%02X%02X:%04X  ",
					(long unsigned int) htonl(s->protinfo.af_ipx.dest_addr.net),
					s->protinfo.af_ipx.dest_addr.node[0],
					s->protinfo.af_ipx.dest_addr.node[1],
					s->protinfo.af_ipx.dest_addr.node[2],
					s->protinfo.af_ipx.dest_addr.node[3],
					s->protinfo.af_ipx.dest_addr.node[4],
					s->protinfo.af_ipx.dest_addr.node[5],
					htons(s->protinfo.af_ipx.dest_addr.sock));
			}

			len += sprintf(buffer+len,"%08X  %08X  ",
				atomic_read(&s->wmem_alloc),
				atomic_read(&s->rmem_alloc));
			len += sprintf(buffer+len,"%02X     %03d\n",
				s->state, SOCK_INODE(s->socket)->i_uid);

			pos = begin + len;
			if(pos < offset)
			{
				len   = 0;
				begin = pos;
			}

			if(pos > offset + length)  /* We have dumped enough */
				break;
		}
	}

	/* The data in question runs from begin to begin+len */
	*start = buffer + (offset-begin);
	len -= (offset - begin);
	if(len > length)
		len = length;

	return (len);
}

static int ipx_rt_get_info(char *buffer, char **start, off_t offset,
			   int length, int dummy)
{
	ipx_route *rt;
	off_t begin = 0, pos = 0;
	int len = 0;

	len += sprintf(buffer,"%-11s%-13s%s\n",
			"Network", "Router_Net", "Router_Node");
	for(rt = ipx_routes; rt != NULL; rt = rt->ir_next)
	{
		len += sprintf(buffer+len,"%08lX   ", (long unsigned int) ntohl(rt->ir_net));
		if(rt->ir_routed)
		{
			len += sprintf(buffer+len,"%08lX     %02X%02X%02X%02X%02X%02X\n",
				(long unsigned int) ntohl(rt->ir_intrfc->if_netnum),
				rt->ir_router_node[0], rt->ir_router_node[1],
				rt->ir_router_node[2], rt->ir_router_node[3],
				rt->ir_router_node[4], rt->ir_router_node[5]);
		}
		else
		{
			len += sprintf(buffer+len, "%-13s%s\n",
					"Directly", "Connected");
		}

		pos = begin + len;
		if(pos < offset)
		{
			len   = 0;
			begin = pos;
		}

		if(pos > offset + length)
			break;
	}

	*start = buffer + (offset - begin);
	len -= (offset - begin);
	if(len > length)
		len = length;

	return (len);
}

/**************************************************************************\
*                                                                          *
* Handling for system calls applied via the various interfaces to an       *
* IPX socket object.                                                       *
*                                                                          *
\**************************************************************************/

static int ipx_setsockopt(struct socket *sock, int level, int optname, char *optval, int optlen)
{
	struct sock *sk;
	int err, opt;

	sk = sock->sk;

	if(optlen != sizeof(int))
		return (-EINVAL);

	err = get_user(opt, (unsigned int *)optval);
	if(err)
		return (err);

	switch(level)
	{
		case SOL_IPX:
			switch(optname)
			{
				case IPX_TYPE:
					sk->protinfo.af_ipx.type = opt;
					return (0);

				default:
					return (-ENOPROTOOPT);
			}
			break;

		default:
			return (-ENOPROTOOPT);
	}
}

static int ipx_getsockopt(struct socket *sock, int level, int optname,
	char *optval, int *optlen)
{
	struct sock *sk;
	int val=0;
	int len;

	sk = sock->sk;

	switch(level)
	{
		case SOL_IPX:
			switch(optname)
			{
				case IPX_TYPE:
					val = sk->protinfo.af_ipx.type;
					break;

				default:
					return (-ENOPROTOOPT);
			}
			break;

		default:
			return (-ENOPROTOOPT);
	}

	if(get_user(len, optlen))
		return (-EFAULT);

	len = min(len, sizeof(int));
	if(put_user(len, optlen))
		return (-EFAULT);

	if(copy_to_user(optval, &val, len))
		return (-EFAULT);

	return (0);
}

static int ipx_create(struct socket *sock, int protocol)
{
	struct sock *sk;

	switch(sock->type)
	{
		case SOCK_DGRAM:
			sk = sk_alloc(PF_IPX, GFP_KERNEL, 1);
			if(sk == NULL)
                		return (-ENOMEM);
                        sock->ops = &ipx_dgram_ops;
                        break;

		case SOCK_SEQPACKET:
			/*
			 * From this point on SPX sockets are handled
			 * by af_spx.c and the methods replaced.
			 */
			if(spx_family_ops)
				return (spx_family_ops->create(sock,protocol));
			/* Fall through if SPX is not loaded */
		case SOCK_STREAM:       /* Allow higher levels to piggyback */
		default:
			return (-ESOCKTNOSUPPORT);
	}

	sock_init_data(sock, sk);
	sk->destruct	= NULL;
	sk->mtu		= IPX_MTU;
	sk->no_check 	= 1;		/* Checksum off by default */

	MOD_INC_USE_COUNT;

	return (0);
}

static int ipx_release(struct socket *sock, struct socket *peer)
{
	struct sock *sk = sock->sk;

	if(sk == NULL)
		return (0);

	if(!sk->dead)
		sk->state_change(sk);

	sk->dead = 1;
	sock->sk = NULL;
	ipx_destroy_socket(sk);

	if(sock->type == SOCK_DGRAM)
		MOD_DEC_USE_COUNT;

	return (0);
}

static unsigned short ipx_first_free_socketnum(ipx_interface *intrfc)
{
	unsigned short socketNum = intrfc->if_sknum;

	if(socketNum < IPX_MIN_EPHEMERAL_SOCKET)
		socketNum = IPX_MIN_EPHEMERAL_SOCKET;

	while(ipxitf_find_socket(intrfc, ntohs(socketNum)) != NULL)
	{
		if(socketNum > IPX_MAX_EPHEMERAL_SOCKET)
			socketNum = IPX_MIN_EPHEMERAL_SOCKET;
		else
			socketNum++;
	}

	intrfc->if_sknum = socketNum;

	return (ntohs(socketNum));
}

static int ipx_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
{
	struct sock *sk;
	ipx_interface *intrfc;
	struct sockaddr_ipx *addr = (struct sockaddr_ipx *)uaddr;

	sk = sock->sk;

	if(sk->zapped == 0)
		return (-EINVAL);

	if(addr_len != sizeof(struct sockaddr_ipx))
		return (-EINVAL);

	intrfc = ipxitf_find_using_net(addr->sipx_network);
	if(intrfc == NULL)
		return (-EADDRNOTAVAIL);

	if(addr->sipx_port == 0)
	{
		addr->sipx_port = ipx_first_free_socketnum(intrfc);
		if(addr->sipx_port == 0)
			return (-EINVAL);
	}

	/* protect IPX system stuff like routing/sap */
	if(ntohs(addr->sipx_port) < IPX_MIN_EPHEMERAL_SOCKET && !suser())
		return (-EACCES);

	sk->protinfo.af_ipx.port = addr->sipx_port;

#ifdef CONFIG_IPX_INTERN
	if(intrfc == ipx_internal_net)
	{
		/* The source address is to be set explicitly if the
		 * socket is to be bound on the internal network. If a
		 * node number 0 was specified, the default is used.
		 */

		if(memcmp(addr->sipx_node,ipx_broadcast_node,IPX_NODE_LEN) == 0)
			return (-EINVAL);
		if(memcmp(addr->sipx_node, ipx_this_node, IPX_NODE_LEN) == 0)
		{
			memcpy(sk->protinfo.af_ipx.node, intrfc->if_node,
			       IPX_NODE_LEN);
		}
		else
		{
			memcpy(sk->protinfo.af_ipx.node, addr->sipx_node, IPX_NODE_LEN);
		}

		if(ipxitf_find_internal_socket(intrfc,
			sk->protinfo.af_ipx.node,
			sk->protinfo.af_ipx.port) != NULL)
		{
			SOCK_DEBUG(sk,
				"IPX: bind failed because port %X in use.\n",
				ntohs((int)addr->sipx_port));
			return (-EADDRINUSE);
		}
	}
	else
	{
		/* Source addresses are easy. It must be our
		 * network:node pair for an interface routed to IPX
		 * with the ipx routing ioctl()
		 */

		memcpy(sk->protinfo.af_ipx.node, intrfc->if_node,
			IPX_NODE_LEN);

		if(ipxitf_find_socket(intrfc, addr->sipx_port) != NULL)
		{
			SOCK_DEBUG(sk,
				"IPX: bind failed because port %X in use.\n",
				ntohs((int)addr->sipx_port));
			return (-EADDRINUSE);
		}
	}

#else	/* !def CONFIG_IPX_INTERN */

	/* Source addresses are easy. It must be our network:node pair for
	   an interface routed to IPX with the ipx routing ioctl() */

	if(ipxitf_find_socket(intrfc, addr->sipx_port) != NULL)
	{
		SOCK_DEBUG(sk, "IPX: bind failed because port %X in use.\n",
				ntohs((int)addr->sipx_port));
		return (-EADDRINUSE);
	}

#endif	/* CONFIG_IPX_INTERN */

	ipxitf_insert_socket(intrfc, sk);
	sk->zapped = 0;
	SOCK_DEBUG(sk, "IPX: bound socket 0x%04X.\n", ntohs(addr->sipx_port) );

	return (0);
}

static int ipx_connect(struct socket *sock, struct sockaddr *uaddr,
	int addr_len, int flags)
{
	struct sock *sk = sock->sk;
	struct sockaddr_ipx *addr;

	sk->state	= TCP_CLOSE;
	sock->state 	= SS_UNCONNECTED;

	if(addr_len != sizeof(*addr))
		return (-EINVAL);
	addr = (struct sockaddr_ipx *)uaddr;

	/* put the autobinding in */
	if(sk->protinfo.af_ipx.port == 0)
	{
		struct sockaddr_ipx uaddr;
		int ret;

		uaddr.sipx_port		= 0;
		uaddr.sipx_network 	= 0;

#ifdef CONFIG_IPX_INTERN
		memcpy(uaddr.sipx_node, sk->protinfo.af_ipx.intrfc->if_node,
		       IPX_NODE_LEN);
#endif	/* CONFIG_IPX_INTERN */

		ret = ipx_bind(sock, (struct sockaddr *)&uaddr,
				sizeof(struct sockaddr_ipx));
		if(ret != 0)
			return (ret);
	}

	if(ipxrtr_lookup(addr->sipx_network) == NULL)
		return (-ENETUNREACH);

	sk->protinfo.af_ipx.dest_addr.net  = addr->sipx_network;
	sk->protinfo.af_ipx.dest_addr.sock = addr->sipx_port;
	memcpy(sk->protinfo.af_ipx.dest_addr.node,
		addr->sipx_node,IPX_NODE_LEN);
	sk->protinfo.af_ipx.type = addr->sipx_type;

	if(sock->type == SOCK_DGRAM )
	{
		sock->state 	= SS_CONNECTED;
		sk->state 	= TCP_ESTABLISHED;
	}

	return (0);
}

static int ipx_accept(struct socket *sock, struct socket *newsock, int flags)
{
	if(newsock->sk)
	{
		sk_free(newsock->sk);
		MOD_DEC_USE_COUNT;
	}

	return (-EOPNOTSUPP);
}

static int ipx_getname(struct socket *sock, struct sockaddr *uaddr,
	int *uaddr_len, int peer)
{
	ipx_address *addr;
	struct sockaddr_ipx sipx;
	struct sock *sk;

	sk = sock->sk;

	*uaddr_len = sizeof(struct sockaddr_ipx);

	if(peer)
	{
		if(sk->state != TCP_ESTABLISHED)
			return (-ENOTCONN);

		addr = &sk->protinfo.af_ipx.dest_addr;
		sipx.sipx_network = addr->net;
		memcpy(sipx.sipx_node,addr->node,IPX_NODE_LEN);
		sipx.sipx_port = addr->sock;
	}
	else
	{
		if(sk->protinfo.af_ipx.intrfc != NULL)
		{
			sipx.sipx_network=sk->protinfo.af_ipx.intrfc->if_netnum;
#ifdef CONFIG_IPX_INTERN
			memcpy(sipx.sipx_node, sk->protinfo.af_ipx.node, IPX_NODE_LEN);
#else
			memcpy(sipx.sipx_node, sk->protinfo.af_ipx.intrfc->if_node, IPX_NODE_LEN);
#endif	/* CONFIG_IPX_INTERN */

		}
		else
		{
			sipx.sipx_network = 0;
			memset(sipx.sipx_node, '\0', IPX_NODE_LEN);
		}

		sipx.sipx_port = sk->protinfo.af_ipx.port;
	}

	sipx.sipx_family = AF_IPX;
	sipx.sipx_type   = sk->protinfo.af_ipx.type;
	memcpy(uaddr,&sipx,sizeof(sipx));

	return (0);
}

int ipx_rcv(struct sk_buff *skb, struct device *dev, struct packet_type *pt)
{
	/* NULL here for pt means the packet was looped back */
	ipx_interface *intrfc;
	struct ipxhdr *ipx;

	ipx = skb->nh.ipxh;
	
	/* Too small? */
	if(ntohs(ipx->ipx_pktsize) < sizeof(struct ipxhdr))
	{
		kfree_skb(skb);
		return (0);
	}
	
	if(ipx->ipx_checksum != IPX_NO_CHECKSUM) 
	{
		if(ipx_set_checksum(ipx, ntohs(ipx->ipx_pktsize)) != ipx->ipx_checksum)
		{
			kfree_skb(skb);
			return (0);
		}
	}

	/* Determine what local ipx endpoint this is */
	intrfc = ipxitf_find_using_phys(dev, pt->type);
	if(intrfc == NULL)
	{
		if(ipxcfg_auto_create_interfaces
		    && ntohl(ipx->ipx_dest.net) != 0L)
		{
			intrfc = ipxitf_auto_create(dev, pt->type);
		}

		if(intrfc == NULL)	/* Not one of ours */
		{
			kfree_skb(skb);
			return (0);
		}
	}

	return (ipxitf_rcv(intrfc, skb));
}

static int ipx_sendmsg(struct socket *sock, struct msghdr *msg, int len,
	struct scm_cookie *scm)
{
	struct sock *sk = sock->sk;
	struct sockaddr_ipx *usipx=(struct sockaddr_ipx *)msg->msg_name;
	struct sockaddr_ipx local_sipx;
	int retval;
	int flags = msg->msg_flags;

	if(sk->zapped)
		return (-EIO);	/* Socket not bound */
	if(flags & ~MSG_DONTWAIT)
		return (-EINVAL);

	if(usipx)
	{
		if(sk->protinfo.af_ipx.port == 0)
		{
			struct sockaddr_ipx uaddr;
			int ret;

			uaddr.sipx_port = 0;
			uaddr.sipx_network = 0L;
#ifdef CONFIG_IPX_INTERN
			memcpy(uaddr.sipx_node, sk->protinfo.af_ipx.intrfc
				->if_node, IPX_NODE_LEN);
#endif
			ret = ipx_bind(sock, (struct sockaddr *)&uaddr,
					sizeof(struct sockaddr_ipx));
			if(ret != 0)
				return (ret);
		}

		if(msg->msg_namelen < sizeof(*usipx))
			return (-EINVAL);
		if(usipx->sipx_family != AF_IPX)
			return (-EINVAL);
	}
	else
	{
		if(sk->state != TCP_ESTABLISHED)
			return (-ENOTCONN);

		usipx=&local_sipx;
		usipx->sipx_family 	= AF_IPX;
		usipx->sipx_type 	= sk->protinfo.af_ipx.type;
		usipx->sipx_port 	= sk->protinfo.af_ipx.dest_addr.sock;
		usipx->sipx_network 	= sk->protinfo.af_ipx.dest_addr.net;
		memcpy(usipx->sipx_node,sk->protinfo.af_ipx.dest_addr.node,IPX_NODE_LEN);
	}

	retval = ipxrtr_route_packet(sk, usipx, msg->msg_iov, len, flags&MSG_DONTWAIT);
	if(retval < 0)
		return (retval);

	return (len);
}


static int ipx_recvmsg(struct socket *sock, struct msghdr *msg, int size,
		int flags, struct scm_cookie *scm)
{
	struct sock *sk = sock->sk;
	struct sockaddr_ipx *sipx = (struct sockaddr_ipx *)msg->msg_name;
	struct ipxhdr *ipx = NULL;
	struct sk_buff *skb;
	int copied, err;

	if(sk->zapped)
		return (-ENOTCONN);

	skb = skb_recv_datagram(sk,flags&~MSG_DONTWAIT,flags&MSG_DONTWAIT,&err);
	if(!skb)
		goto out;

	ipx 	= skb->nh.ipxh;
	copied 	= ntohs(ipx->ipx_pktsize) - sizeof(struct ipxhdr);
	if(copied > size)
	{
		copied=size;
		msg->msg_flags |= MSG_TRUNC;
	}

	err = skb_copy_datagram_iovec(skb, sizeof(struct ipxhdr), msg->msg_iov,
					copied);
	if(err)
		goto out_free;
	sk->stamp = skb->stamp;

	msg->msg_namelen = sizeof(*sipx);

	if(sipx)
	{
		sipx->sipx_family	= AF_IPX;
		sipx->sipx_port		= ipx->ipx_source.sock;
		memcpy(sipx->sipx_node,ipx->ipx_source.node,IPX_NODE_LEN);
		sipx->sipx_network	= ipx->ipx_source.net;
		sipx->sipx_type 	= ipx->ipx_type;
	}
	err = copied;

out_free:
	skb_free_datagram(sk, skb);
out:
	return (err);
}

/*
 * FIXME: We have to really support shutdown.
 */
static int ipx_shutdown(struct socket *sk,int how)
{
	return (-EOPNOTSUPP);
}

static int ipx_ioctl(struct socket *sock,unsigned int cmd, unsigned long arg)
{
	long amount = 0;
	struct sock *sk = sock->sk;

	switch(cmd)
	{
		case TIOCOUTQ:
			amount = sk->sndbuf - atomic_read(&sk->wmem_alloc);
			if(amount < 0)
				amount = 0;
			return (put_user(amount, (int *)arg));

		case TIOCINQ:
		{
			struct sk_buff *skb;
			/* These two are safe on a single CPU system as only user tasks fiddle here */
			if((skb = skb_peek(&sk->receive_queue)) != NULL)
				amount = skb->len - sizeof(struct ipxhdr);
			return (put_user(amount, (int *)arg));
		}

		case SIOCADDRT:
		case SIOCDELRT:
			if(!suser())
				return (-EPERM);
			return (ipxrtr_ioctl(cmd,(void *)arg));

		case SIOCSIFADDR:
		case SIOCAIPXITFCRT:
		case SIOCAIPXPRISLT:
			if(!suser())
				return (-EPERM);

		case SIOCGIFADDR:
			return (ipxitf_ioctl(cmd,(void *)arg));

		case SIOCIPXCFGDATA:
			return (ipxcfg_get_config_data((void *)arg));

		case SIOCGSTAMP:
		{
			int ret = -EINVAL;
			if(sk)
			{
				if(sk->stamp.tv_sec == 0)
					return (-ENOENT);
				ret = -EFAULT;
				if(!copy_to_user((void *)arg, &sk->stamp,
						sizeof(struct timeval)))
					ret = 0;
			}

			return (ret);
		}

		case SIOCGIFDSTADDR:
		case SIOCSIFDSTADDR:
		case SIOCGIFBRDADDR:
		case SIOCSIFBRDADDR:
		case SIOCGIFNETMASK:
		case SIOCSIFNETMASK:
			return (-EINVAL);

		default:
			return (dev_ioctl(cmd,(void *) arg));
	}

	/*NOT REACHED*/
	return (0);
}

/*
 *      SPX interface support
 */

int ipx_register_spx(struct proto_ops **p, struct net_proto_family *spx)
{
        if(spx_family_ops!=NULL)
                return -EBUSY;
        cli();
        MOD_INC_USE_COUNT;
        *p=&ipx_dgram_ops;
        spx_family_ops=spx;
        sti();
        return 0;
}

int ipx_unregister_spx(void)
{
        spx_family_ops=NULL;
        MOD_DEC_USE_COUNT;
        return 0;
}

/*
 * Socket family declarations
 */

static struct net_proto_family ipx_family_ops = {
	PF_IPX,
	ipx_create
};

static struct proto_ops ipx_dgram_ops = {
	PF_IPX,
	sock_no_dup,
	ipx_release,
	ipx_bind,
	ipx_connect,
	sock_no_socketpair,
	ipx_accept,
	ipx_getname,
	datagram_poll,
	ipx_ioctl,
	sock_no_listen,
	ipx_shutdown,
	ipx_setsockopt,
	ipx_getsockopt,
	sock_no_fcntl,
	ipx_sendmsg,
	ipx_recvmsg
};

/* Called by protocol.c on kernel start up */

static struct packet_type ipx_8023_packet_type =

{
	0,	/* MUTTER ntohs(ETH_P_802_3),*/
	NULL,		/* All devices */
	ipx_rcv,
	NULL,
	NULL,
};

static struct packet_type ipx_dix_packet_type =
{
	0,	/* MUTTER ntohs(ETH_P_IPX),*/
	NULL,		/* All devices */
	ipx_rcv,
	NULL,
	NULL,
};

static struct notifier_block ipx_dev_notifier={
	ipxitf_device_event,
	NULL,
	0
};


extern struct datalink_proto *make_EII_client(void);
extern struct datalink_proto *make_8023_client(void);
extern void destroy_EII_client(struct datalink_proto *);
extern void destroy_8023_client(struct datalink_proto *);

struct proc_dir_entry ipx_procinfo = {
	PROC_NET_IPX, 3, "ipx", S_IFREG | S_IRUGO,
	1, 0, 0, 0, &proc_net_inode_operations, ipx_get_info
};

struct proc_dir_entry ipx_if_procinfo = {
	PROC_NET_IPX_INTERFACE, 13, "ipx_interface", S_IFREG | S_IRUGO,
	1, 0, 0, 0, &proc_net_inode_operations, ipx_interface_get_info
};

struct proc_dir_entry ipx_rt_procinfo = {
	PROC_NET_IPX_ROUTE, 9, "ipx_route", S_IFREG | S_IRUGO,
	1, 0, 0, 0, &proc_net_inode_operations, ipx_rt_get_info
};

static unsigned char ipx_8022_type = 0xE0;
static unsigned char ipx_snap_id[5] = { 0x0, 0x0, 0x0, 0x81, 0x37 };

void ipx_proto_init(struct net_proto *pro)
{
	(void) sock_register(&ipx_family_ops);

	pEII_datalink = make_EII_client();
	ipx_dix_packet_type.type = htons(ETH_P_IPX);
	dev_add_pack(&ipx_dix_packet_type);

	p8023_datalink = make_8023_client();
	ipx_8023_packet_type.type = htons(ETH_P_802_3);
	dev_add_pack(&ipx_8023_packet_type);

	if((p8022_datalink = register_8022_client(ipx_8022_type,ipx_rcv)) == NULL)
		printk(KERN_CRIT "IPX: Unable to register with 802.2\n");

	if((p8022tr_datalink = register_8022tr_client(ipx_8022_type,ipx_rcv)) == NULL)
		printk(KERN_CRIT "IPX: Unable to register with 802.2TR\n");

	if((pSNAP_datalink = register_snap_client(ipx_snap_id,ipx_rcv)) == NULL)
		printk(KERN_CRIT "IPX: Unable to register with SNAP\n");

	register_netdevice_notifier(&ipx_dev_notifier);

#ifdef CONFIG_PROC_FS
	proc_net_register(&ipx_procinfo);
	proc_net_register(&ipx_if_procinfo);
	proc_net_register(&ipx_rt_procinfo);
#endif

	printk(KERN_INFO "Swansea University Computer Society IPX 0.38 for NET3.037\n");
	printk(KERN_INFO "IPX Portions Copyright (c) 1995 Caldera, Inc.\n");
}

/* Higher layers need this info to prep tx pkts */
int ipx_if_offset(unsigned long ipx_net_number)
{
	ipx_route *rt = NULL;

	rt = ipxrtr_lookup(ipx_net_number);

	return (rt ? rt->ir_intrfc->if_ipx_offset : -ENETUNREACH);
}

/* Export symbols for higher layers */
EXPORT_SYMBOL(ipxrtr_route_skb);
EXPORT_SYMBOL(ipx_if_offset);
EXPORT_SYMBOL(ipx_remove_socket);
EXPORT_SYMBOL(ipx_register_spx);
EXPORT_SYMBOL(ipx_unregister_spx);

#ifdef MODULE
/* Note on MOD_{INC,DEC}_USE_COUNT:
 *
 * Use counts are incremented/decremented when
 * sockets are created/deleted.
 *
 * Routes are always associated with an interface, and
 * allocs/frees will remain properly accounted for by
 * their associated interfaces.
 *
 * Ergo, before the ipx module can be removed, all IPX
 * sockets be closed from user space.
 */

static void ipx_proto_finito(void)
{
	ipx_interface	*ifc;

	while(ipx_interfaces)
	{
		ifc = ipx_interfaces;
		ipx_interfaces 	= ifc->if_next;
		ifc->if_next 	= NULL;
		ipxitf_down(ifc);
	}

#ifdef CONFIG_PROC_FS
	proc_net_unregister(PROC_NET_IPX_ROUTE);
	proc_net_unregister(PROC_NET_IPX_INTERFACE);
	proc_net_unregister(PROC_NET_IPX);
#endif

	unregister_netdevice_notifier(&ipx_dev_notifier);

	unregister_snap_client(ipx_snap_id);
	pSNAP_datalink 	= NULL;

	unregister_8022tr_client(ipx_8022_type);
	p8022tr_datalink = NULL;

	unregister_8022_client(ipx_8022_type);
	p8022_datalink 	= NULL;

	dev_remove_pack(&ipx_8023_packet_type);
	destroy_8023_client(p8023_datalink);
	p8023_datalink 	= NULL;

	dev_remove_pack(&ipx_dix_packet_type);
	destroy_EII_client(pEII_datalink);
	pEII_datalink 	= NULL;

	(void) sock_unregister(ipx_family_ops.family);

	return;
}

int init_module(void)
{
	ipx_proto_init(NULL);
	return (0);
}

void cleanup_module(void)
{
	ipx_proto_finito();
	return;
}

#endif /* MODULE */
#endif /* CONFIG_IPX || CONFIG_IPX_MODULE */
