diff options
author | Michael I. Bushnell <mib@gnu.org> | 1995-07-12 15:42:50 +0000 |
---|---|---|
committer | Michael I. Bushnell <mib@gnu.org> | 1995-07-12 15:42:50 +0000 |
commit | a81cd86c8d93236ffccfbee44b5818ba21523463 (patch) | |
tree | f098368d79e9ed9b39907730c28ed3182b69519d /pfinet/linux-inet/ipx.c | |
parent | c7923f6aa252a29ccb4f16bd91469c9000a2bd94 (diff) |
entered into RCS
Diffstat (limited to 'pfinet/linux-inet/ipx.c')
-rw-r--r-- | pfinet/linux-inet/ipx.c | 1947 |
1 files changed, 1947 insertions, 0 deletions
diff --git a/pfinet/linux-inet/ipx.c b/pfinet/linux-inet/ipx.c new file mode 100644 index 00000000..88b53c30 --- /dev/null +++ b/pfinet/linux-inet/ipx.c @@ -0,0 +1,1947 @@ +/* + * Implements an IPX socket layer (badly - but I'm working on it). + * + * 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 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 modules) + * 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> + * + * 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> +#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/ipx.h> +#include <linux/inet.h> +#include <linux/netdevice.h> +#include <linux/skbuff.h> +#include "sock.h" +#include <asm/segment.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 "p8022.h" +#include "psnap.h" + +#ifdef CONFIG_IPX +/* 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 *pEII_datalink = NULL; +static struct datalink_proto *p8023_datalink = NULL; +static struct datalink_proto *pSNAP_datalink = NULL; + +static ipx_interface *ipx_interfaces = NULL; +static ipx_route *ipx_routes = NULL; +static ipx_interface *ipx_internal_net = NULL; +static ipx_interface *ipx_primary_net = NULL; + +static int +ipxcfg_set_auto_create(char val) +{ + 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; + memcpy_tofs(arg, &vals, sizeof(vals)); + return 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. + */ + +static void +ipx_remove_socket(ipx_socket *sk) +{ + ipx_socket *s; + ipx_interface *intrfc; + unsigned long flags; + + save_flags(flags); + cli(); + + /* Determine interface with which socket is associated */ + intrfc = sk->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(ipx_socket *sk) +{ + struct sk_buff *skb; + + ipx_remove_socket(sk); + while((skb=skb_dequeue(&sk->receive_queue))!=NULL) { + kfree_skb(skb,FREE_READ); + } + + kfree_s(sk,sizeof(*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(unsigned long); + +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(unsigned long net) +{ + ipx_interface *i; + + if (net == 0L) + 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, ipx_socket *sk) +{ + ipx_socket *s; + + sk->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 ipx_socket * +ipxitf_find_socket(ipx_interface *intrfc, unsigned short port) +{ + ipx_socket *s; + + for (s=intrfc->if_sklist; + (s != NULL) && (s->ipx_port != port); + s=s->next) + ; + + return s; +} + +static void ipxrtr_del_routes(ipx_interface *); + +static void +ipxitf_down(ipx_interface *intrfc) +{ + ipx_interface *i; + ipx_socket *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->ipx_intrfc = NULL; + s->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)); +} + +static int +ipxitf_device_event(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) { + /* + * We do a FREE_WRITE here because this indicates how + * to treat the socket with which the packet is + * associated. If this packet is associated with a + * socket at all, it must be the originator of the + * packet. Incoming packets will have no socket + * associated with them at this point. + */ + kfree_skb(skb,FREE_WRITE); + } + return retval; +} + +static int +ipxitf_demux_socket(ipx_interface *intrfc, struct sk_buff *skb, int copy) +{ + ipx_packet *ipx = (ipx_packet *)(skb->h.raw); + ipx_socket *sock1 = NULL, *sock2 = NULL; + struct sk_buff *skb1 = NULL, *skb2 = NULL; + int ipx_offset; + + 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 */ + if ((sock1 == NULL) && (sock2 == NULL)) { + if (!copy) + kfree_skb(skb,FREE_WRITE); + return 0; + } + + ipx_offset = (char *)(skb->h.raw) - (char *)(skb->data); + + /* 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); + if (skb1 != NULL) { + skb1->h.raw = (unsigned char *)&(skb1->data[ipx_offset]); + skb1->arp = skb1->free = 1; + } + } else { + skb1 = skb; + } + + if (skb1 == NULL) return -ENOMEM; + + /* Do we need 2 SKBs? */ + if (sock1 && sock2) { + skb2 = skb_clone(skb1, GFP_ATOMIC); + if (skb2 != NULL) { + skb2->h.raw = (unsigned char *)&(skb2->data[ipx_offset]); + skb2->arp = skb2->free = 1; + } + } 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; +} + +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->data; + int out_offset = intrfc->if_ipx_offset; + char *oldraw; + int len; + + /* Hopefully, most cases */ + if (in_offset == out_offset) { + skb->len += out_offset; + skb->arp = skb->free = 1; + return skb; + } + + /* Existing SKB will work, just need to move things around a little */ + if (in_offset > out_offset) { + oldraw = skb->h.raw; + skb->h.raw = &(skb->data[out_offset]); + memmove(skb->h.raw, oldraw, skb->len); + skb->len += out_offset; + skb->arp = skb->free = 1; + return skb; + } + + /* Need new SKB */ + len = skb->len + out_offset; + skb2 = alloc_skb(len, GFP_ATOMIC); + if (skb2 != NULL) { + skb2->h.raw = &(skb2->data[out_offset]); + skb2->len = len; + skb2->free=1; + skb2->arp=1; + memcpy(skb2->h.raw, skb->h.raw, skb->len); + } + kfree_skb(skb, FREE_WRITE); + return skb2; +} + +static int +ipxitf_send(ipx_interface *intrfc, struct sk_buff *skb, char *node) +{ + ipx_packet *ipx = (ipx_packet *)(skb->h.raw); + 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; + + /* See if this should be demuxed to sockets on this interface */ + if (ipx->ipx_dest.net == intrfc->if_netnum) { + if (memcmp(intrfc->if_node, node, IPX_NODE_LEN) == 0) + return ipxitf_demux_socket(intrfc, skb, 0); + if (memcmp(ipx_broadcast_node, node, IPX_NODE_LEN) == 0) { + 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 */ + if (ipx->ipx_source.net != intrfc->if_netnum) { + if (++(ipx->ipx_tctrl) > ipxcfg_max_hops) + send_to_wire = 0; + } + + if (!send_to_wire) { + /* + * We do a FREE_WRITE here because this indicates how + * to treat the socket with which the packet is + * associated. If this packet is associated with a + * socket at all, it must be the originator of the + * packet. Routed packets will have no socket associated + * with them. + */ + kfree_skb(skb,FREE_WRITE); + 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; + dl->datalink_header(dl, skb, dest_node); + + if (skb->sk != NULL) { + /* This is an outbound packet from this host. We need to + * increment the write count. + */ + skb->sk->wmem_alloc += skb->mem_len; + } + + /* Send it out */ + dev_queue_xmit(skb, dev, SOPRI_NORMAL); + return 0; +} + +static int +ipxrtr_add_route(unsigned long, ipx_interface *, unsigned char *); + +static int +ipxitf_add_local_route(ipx_interface *intrfc) +{ + return ipxrtr_add_route(intrfc->if_netnum, intrfc, NULL); +} + +static char * ipx_frame_name(unsigned short); +static char * ipx_device_name(ipx_interface *); +static int ipxrtr_route_skb(struct sk_buff *); + +static int +ipxitf_rcv(ipx_interface *intrfc, struct sk_buff *skb) +{ + ipx_packet *ipx = (ipx_packet *) (skb->h.raw); + ipx_interface *i; + + /* See if we should update our network number */ + if ((intrfc->if_netnum == 0L) && + (ipx->ipx_source.net == ipx->ipx_dest.net) && + (ipx->ipx_source.net != 0L)) { + /* 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("IPX: Network number collision %lx\n\t%s %s and %s %s\n", + 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_dest.net == 0L) + ipx->ipx_dest.net = intrfc->if_netnum; + if (ipx->ipx_source.net == 0L) + ipx->ipx_source.net = intrfc->if_netnum; + + if (intrfc->if_netnum != ipx->ipx_dest.net) { + /* We only route point-to-point packets. */ + if ((skb->pkt_type != PACKET_BROADCAST) && + (skb->pkt_type != PACKET_MULTICAST)) + return ipxrtr_route_skb(skb); + + kfree_skb(skb,FREE_READ); + 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,FREE_READ); + 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; +} + +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 == 0L) 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_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 != 0L) && + (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_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; + 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); + } + + /* If the network number is known, add a route */ + if (intrfc->if_netnum == 0L) + 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_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=0L; + 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) +{ + int err; + switch(cmd) + { + case SIOCSIFADDR: + { + struct ifreq ifr; + struct sockaddr_ipx *sipx; + ipx_interface_definition f; + err=verify_area(VERIFY_READ,arg,sizeof(ifr)); + if(err) + return err; + memcpy_fromfs(&ifr,arg,sizeof(ifr)); + 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 ifreq ifr; + struct sockaddr_ipx *sipx; + ipx_interface *ipxif; + struct device *dev; + err=verify_area(VERIFY_WRITE,arg,sizeof(ifr)); + if(err) + return err; + memcpy_fromfs(&ifr,arg,sizeof(ifr)); + 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_network=ipxif->if_netnum; + memcpy(sipx->sipx_node, ipxif->if_node, sizeof(sipx->sipx_node)); + memcpy_tofs(arg,&ifr,sizeof(ifr)); + return 0; + } + case SIOCAIPXITFCRT: + err=verify_area(VERIFY_READ,arg,sizeof(char)); + if(err) + return err; + return ipxcfg_set_auto_create(get_fs_byte(arg)); + case SIOCAIPXPRISLT: + err=verify_area(VERIFY_READ,arg,sizeof(char)); + if(err) + return err; + return ipxcfg_set_auto_select(get_fs_byte(arg)); + default: + return -EINVAL; + } +} + +/*******************************************************************************************************************\ +* * +* Routing tables for the IPX socket layer * +* * +\*******************************************************************************************************************/ + +static ipx_route * +ipxrtr_lookup(unsigned long 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(unsigned long 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; + } + + 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) { + if (!(tmp->ir_routed)) { + /* Directly connected; can't lose route */ + return -EPERM; + } + *r = tmp->ir_next; + kfree_s(tmp, sizeof(ipx_route)); + return 0; + } + r = &(tmp->ir_next); + } + + return -ENOENT; +} + +static int +ipxrtr_route_packet(ipx_socket *sk, struct sockaddr_ipx *usipx, void *ubuf, int len) +{ + struct sk_buff *skb; + ipx_interface *intrfc; + ipx_packet *ipx; + int size; + int ipx_offset; + ipx_route *rt = NULL; + + /* Find the appropriate interface on which to send packet */ + if ((usipx->sipx_network == 0L) && (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(ipx_packet)+len; + size += ipx_offset; + + if(size+sk->wmem_alloc>sk->sndbuf) return -EAGAIN; + + skb=alloc_skb(size,GFP_KERNEL); + if(skb==NULL) return -ENOMEM; + + skb->sk=sk; + skb->len=size; + skb->free=1; + skb->arp=1; + + /* Fill in IPX header */ + ipx=(ipx_packet *)&(skb->data[ipx_offset]); + ipx->ipx_checksum=0xFFFF; + ipx->ipx_pktsize=htons(len+sizeof(ipx_packet)); + ipx->ipx_tctrl=0; + ipx->ipx_type=usipx->sipx_type; + skb->h.raw = (unsigned char *)ipx; + + ipx->ipx_source.net = sk->ipx_intrfc->if_netnum; + memcpy(ipx->ipx_source.node, sk->ipx_intrfc->if_node, IPX_NODE_LEN); + ipx->ipx_source.sock = sk->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; + + memcpy_fromfs((char *)(ipx+1),ubuf,len); + return ipxitf_send(intrfc, skb, (rt && rt->ir_routed) ? + rt->ir_router_node : ipx->ipx_dest.node); +} + +static int +ipxrtr_route_skb(struct sk_buff *skb) +{ + ipx_packet *ipx = (ipx_packet *) (skb->h.raw); + ipx_route *r; + ipx_interface *i; + + r = ipxrtr_lookup(ipx->ipx_dest.net); + if (r == NULL) { + /* no known route */ + kfree_skb(skb,FREE_READ); + 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) +{ + int err; + struct rtentry rt; /* Use these to behave like 'other' stacks */ + struct sockaddr_ipx *sg,*st; + + err=verify_area(VERIFY_READ,arg,sizeof(rt)); + if(err) + return err; + + memcpy_fromfs(&rt,arg,sizeof(rt)); + + 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 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"; + default: return "None"; + } +} + +static char * +ipx_device_name(ipx_interface *intrfc) +{ + return (intrfc->if_internal ? "Internal" : + (intrfc->if_dev ? intrfc->if_dev->name : "Unknown")); +} + +/* Called from proc fs */ +int +ipx_get_interface_info(char *buffer, char **start, off_t offset, int length) +{ + ipx_interface *i; + int len=0; + off_t pos=0; + off_t begin=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 ", 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; +} + +int +ipx_get_info(char *buffer, char **start, off_t offset, int length) +{ + ipx_socket *s; + ipx_interface *i; + int len=0; + off_t pos=0; + off_t begin=0; + + /* Theory.. Keep printing in the same place until we pass offset */ + + len += sprintf (buffer,"%-15s%-28s%-10s%-10s%-7s%s\n", "Local_Address", + "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) { + len += sprintf (buffer+len,"%08lX:%04X ", + htonl(i->if_netnum), + htons(s->ipx_port)); + 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 ", + htonl(s->ipx_dest_addr.net), + s->ipx_dest_addr.node[0], s->ipx_dest_addr.node[1], + s->ipx_dest_addr.node[2], s->ipx_dest_addr.node[3], + s->ipx_dest_addr.node[4], s->ipx_dest_addr.node[5], + htons(s->ipx_dest_addr.sock)); + } + len += sprintf (buffer+len,"%08lX %08lX ", + s->wmem_alloc, s->rmem_alloc); + len += sprintf (buffer+len,"%02X %03d\n", + s->state, SOCK_INODE(s->socket)->i_uid); + + /* 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; +} + +int ipx_rt_get_info(char *buffer, char **start, off_t offset, int length) +{ + ipx_route *rt; + int len=0; + off_t pos=0; + off_t begin=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 ", ntohl(rt->ir_net)); + if (rt->ir_routed) { + len += sprintf (buffer+len,"%08lX %02X%02X%02X%02X%02X%02X\n", + 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_fcntl(struct socket *sock, unsigned int cmd, unsigned long arg) +{ + switch(cmd) + { + default: + return(-EINVAL); + } +} + +static int ipx_setsockopt(struct socket *sock, int level, int optname, char *optval, int optlen) +{ + ipx_socket *sk; + int err,opt; + + sk=(ipx_socket *)sock->data; + + if(optval==NULL) + return(-EINVAL); + + err=verify_area(VERIFY_READ,optval,sizeof(int)); + if(err) + return err; + opt=get_fs_long((unsigned long *)optval); + + switch(level) + { + case SOL_IPX: + switch(optname) + { + case IPX_TYPE: + sk->ipx_type=opt; + return 0; + default: + return -EOPNOTSUPP; + } + break; + + case SOL_SOCKET: + return sock_setsockopt(sk,level,optname,optval,optlen); + + default: + return -EOPNOTSUPP; + } +} + +static int ipx_getsockopt(struct socket *sock, int level, int optname, + char *optval, int *optlen) +{ + ipx_socket *sk; + int val=0; + int err; + + sk=(ipx_socket *)sock->data; + + switch(level) + { + + case SOL_IPX: + switch(optname) + { + case IPX_TYPE: + val=sk->ipx_type; + break; + default: + return -ENOPROTOOPT; + } + break; + + case SOL_SOCKET: + return sock_getsockopt(sk,level,optname,optval,optlen); + + default: + return -EOPNOTSUPP; + } + err=verify_area(VERIFY_WRITE,optlen,sizeof(int)); + if(err) + return err; + put_fs_long(sizeof(int),(unsigned long *)optlen); + err=verify_area(VERIFY_WRITE,optval,sizeof(int)); + put_fs_long(val,(unsigned long *)optval); + return(0); +} + +static int ipx_listen(struct socket *sock, int backlog) +{ + return -EOPNOTSUPP; +} + +static void def_callback1(struct sock *sk) +{ + if(!sk->dead) + wake_up_interruptible(sk->sleep); +} + +static void def_callback2(struct sock *sk, int len) +{ + if(!sk->dead) + { + wake_up_interruptible(sk->sleep); + sock_wake_async(sk->socket, 1); + } +} + +static int +ipx_create(struct socket *sock, int protocol) +{ + ipx_socket *sk; + sk=(ipx_socket *)kmalloc(sizeof(*sk),GFP_KERNEL); + if(sk==NULL) + return(-ENOMEM); + switch(sock->type) + { + case SOCK_DGRAM: + break; + default: + kfree_s((void *)sk,sizeof(*sk)); + return(-ESOCKTNOSUPPORT); + } + sk->dead=0; + sk->next=NULL; + sk->broadcast=0; + sk->rcvbuf=SK_RMEM_MAX; + sk->sndbuf=SK_WMEM_MAX; + sk->wmem_alloc=0; + sk->rmem_alloc=0; + sk->inuse=0; + sk->shutdown=0; + sk->prot=NULL; /* So we use default free mechanisms */ + sk->broadcast=0; + sk->err=0; + skb_queue_head_init(&sk->receive_queue); + skb_queue_head_init(&sk->write_queue); + sk->send_head=NULL; + skb_queue_head_init(&sk->back_log); + sk->state=TCP_CLOSE; + sk->socket=sock; + sk->type=sock->type; + sk->ipx_type=0; /* General user level IPX */ + sk->debug=0; + sk->ipx_intrfc = NULL; + memset(&sk->ipx_dest_addr,'\0',sizeof(sk->ipx_dest_addr)); + sk->ipx_port = 0; + sk->mtu=IPX_MTU; + + if(sock!=NULL) + { + sock->data=(void *)sk; + sk->sleep=sock->wait; + } + + sk->state_change=def_callback1; + sk->data_ready=def_callback2; + sk->write_space=def_callback1; + sk->error_report=def_callback1; + + sk->zapped=1; + return 0; +} + +static int ipx_release(struct socket *sock, struct socket *peer) +{ + ipx_socket *sk=(ipx_socket *)sock->data; + if(sk==NULL) + return(0); + if(!sk->dead) + sk->state_change(sk); + sk->dead=1; + sock->data=NULL; + ipx_destroy_socket(sk); + return(0); +} + +static int ipx_dup(struct socket *newsock,struct socket *oldsock) +{ + return(ipx_create(newsock,SOCK_DGRAM)); +} + +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) +{ + ipx_socket *sk; + ipx_interface *intrfc; + struct sockaddr_ipx *addr=(struct sockaddr_ipx *)uaddr; + + sk=(ipx_socket *)sock->data; + + if(sk->zapped==0) + return -EIO; + + 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; + } + + if(ntohs(addr->sipx_port)<IPX_MIN_EPHEMERAL_SOCKET && !suser()) + return -EPERM; /* protect IPX system stuff like routing/sap */ + + /* 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) { + if(sk->debug) + printk("IPX: bind failed because port %X in use.\n", + (int)addr->sipx_port); + return -EADDRINUSE; + } + + sk->ipx_port=addr->sipx_port; + ipxitf_insert_socket(intrfc, sk); + sk->zapped=0; + if(sk->debug) + printk("IPX: socket is bound.\n"); + return 0; +} + +static int ipx_connect(struct socket *sock, struct sockaddr *uaddr, + int addr_len, int flags) +{ + ipx_socket *sk=(ipx_socket *)sock->data; + struct sockaddr_ipx *addr; + + sk->state = TCP_CLOSE; + sock->state = SS_UNCONNECTED; + + if(addr_len!=sizeof(*addr)) + return(-EINVAL); + addr=(struct sockaddr_ipx *)uaddr; + + if(sk->ipx_port==0) + /* put the autobinding in */ + { + struct sockaddr_ipx uaddr; + int ret; + + uaddr.sipx_port = 0; + uaddr.sipx_network = 0L; + 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->ipx_dest_addr.net=addr->sipx_network; + sk->ipx_dest_addr.sock=addr->sipx_port; + memcpy(sk->ipx_dest_addr.node,addr->sipx_node,IPX_NODE_LEN); + sk->ipx_type=addr->sipx_type; + sock->state = SS_CONNECTED; + sk->state=TCP_ESTABLISHED; + return 0; +} + +static int ipx_socketpair(struct socket *sock1, struct socket *sock2) +{ + return(-EOPNOTSUPP); +} + +static int ipx_accept(struct socket *sock, struct socket *newsock, int flags) +{ + if(newsock->data) + kfree_s(newsock->data,sizeof(ipx_socket)); + return -EOPNOTSUPP; +} + +static int ipx_getname(struct socket *sock, struct sockaddr *uaddr, + int *uaddr_len, int peer) +{ + ipx_address *addr; + struct sockaddr_ipx sipx; + ipx_socket *sk; + + sk=(ipx_socket *)sock->data; + + *uaddr_len = sizeof(struct sockaddr_ipx); + + if(peer) { + if(sk->state!=TCP_ESTABLISHED) + return -ENOTCONN; + addr=&sk->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->ipx_intrfc != NULL) { + sipx.sipx_network = sk->ipx_intrfc->if_netnum; + memcpy(sipx.sipx_node, sk->ipx_intrfc->if_node, + IPX_NODE_LEN); + } else { + sipx.sipx_network = 0L; + memset(sipx.sipx_node, '\0', IPX_NODE_LEN); + } + sipx.sipx_port = sk->ipx_port; + } + + sipx.sipx_family = AF_IPX; + sipx.sipx_type = sk->ipx_type; + memcpy(uaddr,&sipx,sizeof(sipx)); + return 0; +} + +#if 0 +/* + * User to dump IPX packets (debugging) + */ +void dump_data(char *str,unsigned char *d) { + static char h2c[] = "0123456789ABCDEF"; + int l,i; + char *p, b[64]; + for (l=0;l<16;l++) { + p = b; + for (i=0; i < 8 ; i++) { + *(p++) = h2c[d[i] & 0x0f]; + *(p++) = h2c[(d[i] >> 4) & 0x0f]; + *(p++) = ' '; + } + *(p++) = '-'; + *(p++) = ' '; + for (i=0; i < 8 ; i++) *(p++) = ' '<= d[i] && d[i]<'\177' ? d[i] : '.'; + *p = '\000'; + d += i; + printk("%s-%04X: %s\n",str,l*8,b); + } +} + +void dump_addr(char *str,ipx_address *p) { + printk("%s: %08X:%02X%02X%02X%02X%02X%02X:%04X\n", + str,ntohl(p->net),p->node[0],p->node[1],p->node[2], + p->node[3],p->node[4],p->node[5],ntohs(p->sock)); +} + +void dump_hdr(char *str,ipx_packet *p) { + printk("%s: CHKSUM=%04X SIZE=%d (%04X) HOPS=%d (%02X) TYPE=%02X\n", + str,p->ipx_checksum,ntohs(p->ipx_pktsize),ntohs(p->ipx_pktsize), + p->ipx_tctrl,p->ipx_tctrl,p->ipx_type); + dump_addr(" IPX-DST",&p->ipx_dest); + dump_addr(" IPX-SRC",&p->ipx_source); +} + +void dump_pkt(char *str,ipx_packet *p) { + dump_hdr(str,p); + dump_data(str,(unsigned char *)p); +} +#endif + +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; + ipx_packet *ipx; + + ipx=(ipx_packet *)skb->h.raw; + + if(ipx->ipx_checksum!=IPX_NO_CHECKSUM) { + /* We don't do checksum options. We can't really. Novell don't seem to have documented them. + If you need them try the XNS checksum since IPX is basically XNS in disguise. It might be + the same... */ + kfree_skb(skb,FREE_READ); + return 0; + } + + /* Too small */ + if(htons(ipx->ipx_pktsize)<sizeof(ipx_packet)) { + kfree_skb(skb,FREE_READ); + 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) { + intrfc = ipxitf_auto_create(dev, pt->type); + } + + if (intrfc == NULL) { + /* Not one of ours */ + kfree_skb(skb,FREE_READ); + return 0; + } + } + + return ipxitf_rcv(intrfc, skb); +} + +static int ipx_sendto(struct socket *sock, void *ubuf, int len, int noblock, + unsigned flags, struct sockaddr *usip, int addr_len) +{ + ipx_socket *sk=(ipx_socket *)sock->data; + struct sockaddr_ipx *usipx=(struct sockaddr_ipx *)usip; + struct sockaddr_ipx local_sipx; + int retval; + + if (sk->zapped) return -EIO; /* Socket not bound */ + if(flags) return -EINVAL; + + if(usipx) { + if(sk->ipx_port == 0) { + struct sockaddr_ipx uaddr; + int ret; + + uaddr.sipx_port = 0; + uaddr.sipx_network = 0L; + ret = ipx_bind (sock, (struct sockaddr *)&uaddr, sizeof(struct sockaddr_ipx)); + if (ret != 0) return ret; + } + + if(addr_len <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->ipx_type; + usipx->sipx_port=sk->ipx_dest_addr.sock; + usipx->sipx_network=sk->ipx_dest_addr.net; + memcpy(usipx->sipx_node,sk->ipx_dest_addr.node,IPX_NODE_LEN); + } + + retval = ipxrtr_route_packet(sk, usipx, ubuf, len); + if (retval < 0) return retval; + + return len; +} + +static int ipx_send(struct socket *sock, void *ubuf, int size, int noblock, unsigned flags) +{ + return ipx_sendto(sock,ubuf,size,noblock,flags,NULL,0); +} + +static int ipx_recvfrom(struct socket *sock, void *ubuf, int size, int noblock, + unsigned flags, struct sockaddr *sip, int *addr_len) +{ + ipx_socket *sk=(ipx_socket *)sock->data; + struct sockaddr_ipx *sipx=(struct sockaddr_ipx *)sip; + struct ipx_packet *ipx = NULL; + int copied = 0; + int truesize; + struct sk_buff *skb; + int er; + + if(sk->err) + { + er= -sk->err; + sk->err=0; + return er; + } + + if (sk->zapped) + return -EIO; + + if(addr_len) + *addr_len=sizeof(*sipx); + + skb=skb_recv_datagram(sk,flags,noblock,&er); + if(skb==NULL) + return er; + + ipx = (ipx_packet *)(skb->h.raw); + truesize=ntohs(ipx->ipx_pktsize) - sizeof(ipx_packet); + copied = (truesize > size) ? size : truesize; + skb_copy_datagram(skb,sizeof(struct ipx_packet),ubuf,copied); + + 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; + } + skb_free_datagram(skb); + return(truesize); +} + +static int ipx_write(struct socket *sock, char *ubuf, int size, int noblock) +{ + return ipx_send(sock,ubuf,size,noblock,0); +} + + +static int ipx_recv(struct socket *sock, void *ubuf, int size , int noblock, + unsigned flags) +{ + ipx_socket *sk=(ipx_socket *)sock->data; + if(sk->zapped) + return -ENOTCONN; + return ipx_recvfrom(sock,ubuf,size,noblock,flags,NULL, NULL); +} + +static int ipx_read(struct socket *sock, char *ubuf, int size, int noblock) +{ + return ipx_recv(sock,ubuf,size,noblock,0); +} + + +static int ipx_shutdown(struct socket *sk,int how) +{ + return -EOPNOTSUPP; +} + +static int ipx_select(struct socket *sock , int sel_type, select_table *wait) +{ + ipx_socket *sk=(ipx_socket *)sock->data; + + return datagram_select(sk,sel_type,wait); +} + +static int ipx_ioctl(struct socket *sock,unsigned int cmd, unsigned long arg) +{ + int err; + long amount=0; + ipx_socket *sk=(ipx_socket *)sock->data; + + switch(cmd) + { + case TIOCOUTQ: + err=verify_area(VERIFY_WRITE,(void *)arg,sizeof(unsigned long)); + if(err) + return err; + amount=sk->sndbuf-sk->wmem_alloc; + if(amount<0) + amount=0; + put_fs_long(amount,(unsigned long *)arg); + return 0; + 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; + err=verify_area(VERIFY_WRITE,(void *)arg,sizeof(unsigned long)); + put_fs_long(amount,(unsigned long *)arg); + return 0; + } + case SIOCADDRT: + case SIOCDELRT: + if(!suser()) + return -EPERM; + return(ipxrtr_ioctl(cmd,(void *)arg)); + case SIOCSIFADDR: + case SIOCGIFADDR: + case SIOCAIPXITFCRT: + case SIOCAIPXPRISLT: + if(!suser()) + return -EPERM; + return(ipxitf_ioctl(cmd,(void *)arg)); + case SIOCIPXCFGDATA: + { + err=verify_area(VERIFY_WRITE,(void *)arg, + sizeof(ipx_config_data)); + if(err) return err; + return(ipxcfg_get_config_data((void *)arg)); + } + case SIOCGSTAMP: + if (sk) + { + if(sk->stamp.tv_sec==0) + return -ENOENT; + err=verify_area(VERIFY_WRITE,(void *)arg,sizeof(struct timeval)); + if(err) + return err; + memcpy_tofs((void *)arg,&sk->stamp,sizeof(struct timeval)); + return 0; + } + return -EINVAL; + case SIOCGIFDSTADDR: + case SIOCSIFDSTADDR: + case SIOCGIFBRDADDR: + case SIOCSIFBRDADDR: + case SIOCGIFNETMASK: + case SIOCSIFNETMASK: + return -EINVAL; + default: + return(dev_ioctl(cmd,(void *) arg)); + } + /*NOTREACHED*/ + return(0); +} + +static struct proto_ops ipx_proto_ops = { + AF_IPX, + + ipx_create, + ipx_dup, + ipx_release, + ipx_bind, + ipx_connect, + ipx_socketpair, + ipx_accept, + ipx_getname, + ipx_read, + ipx_write, + ipx_select, + ipx_ioctl, + ipx_listen, + ipx_send, + ipx_recv, + ipx_sendto, + ipx_recvfrom, + ipx_shutdown, + ipx_setsockopt, + ipx_getsockopt, + ipx_fcntl, +}; + +/* Called by ddi.c on kernel start up */ + +static struct packet_type ipx_8023_packet_type = + +{ + 0, /* MUTTER ntohs(ETH_P_8023),*/ + 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); + +void ipx_proto_init(struct net_proto *pro) +{ + unsigned char val = 0xE0; + unsigned char snapval[5] = { 0x0, 0x0, 0x0, 0x81, 0x37 }; + + (void) sock_register(ipx_proto_ops.family, &ipx_proto_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(val, ipx_rcv)) == NULL) + printk("IPX: Unable to register with 802.2\n"); + + if ((pSNAP_datalink = register_snap_client(snapval, ipx_rcv)) == NULL) + printk("IPX: Unable to register with SNAP\n"); + + register_netdevice_notifier(&ipx_dev_notifier); + + printk("Swansea University Computer Society IPX 0.29 BETA for NET3.019\n"); + printk("IPX Portions Copyright (c) 1995 Caldera, Inc.\n"); +} +#endif |