diff options
Diffstat (limited to 'net/ipx/af_ipx.c')
-rw-r--r-- | net/ipx/af_ipx.c | 2024 |
1 files changed, 2024 insertions, 0 deletions
diff --git a/net/ipx/af_ipx.c b/net/ipx/af_ipx.c new file mode 100644 index 00000000000..5a27e5df588 --- /dev/null +++ b/net/ipx/af_ipx.c @@ -0,0 +1,2024 @@ +/* + * 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. + * + * Portions Copyright (c) 2000-2003 Conectiva, Inc. <acme@conectiva.com.br> + * Neither Arnaldo Carvalho de Melo nor Conectiva, Inc. admit liability nor + * provide warranty for any of this software. This material is provided + * "AS-IS" and at no charge. + * + * 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. + * + * See net/ipx/ChangeLog. + */ + +#include <linux/config.h> +#include <linux/errno.h> +#include <linux/if_arp.h> +#include <linux/if_ether.h> +#include <linux/init.h> +#include <linux/ipx.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/net.h> +#include <linux/netdevice.h> +#include <linux/uio.h> +#include <linux/skbuff.h> +#include <linux/socket.h> +#include <linux/sockios.h> +#include <linux/string.h> +#include <linux/tcp.h> +#include <linux/types.h> +#include <linux/termios.h> + +#include <net/ipx.h> +#include <net/p8022.h> +#include <net/psnap.h> +#include <net/sock.h> + +#include <asm/uaccess.h> + +#ifdef CONFIG_SYSCTL +extern void ipx_register_sysctl(void); +extern void ipx_unregister_sysctl(void); +#else +#define ipx_register_sysctl() +#define ipx_unregister_sysctl() +#endif + +/* Configuration Variables */ +static unsigned char ipxcfg_max_hops = 16; +static char ipxcfg_auto_select_primary; +static char ipxcfg_auto_create_interfaces; +int sysctl_ipx_pprop_broadcasting = 1; + +/* Global Variables */ +static struct datalink_proto *p8022_datalink; +static struct datalink_proto *pEII_datalink; +static struct datalink_proto *p8023_datalink; +static struct datalink_proto *pSNAP_datalink; + +static struct proto_ops ipx_dgram_ops; + +LIST_HEAD(ipx_interfaces); +DEFINE_SPINLOCK(ipx_interfaces_lock); + +struct ipx_interface *ipx_primary_net; +struct ipx_interface *ipx_internal_net; + +extern int ipxrtr_add_route(__u32 network, struct ipx_interface *intrfc, + unsigned char *node); +extern void ipxrtr_del_routes(struct ipx_interface *intrfc); +extern int ipxrtr_route_packet(struct sock *sk, struct sockaddr_ipx *usipx, + struct iovec *iov, int len, int noblock); +extern int ipxrtr_route_skb(struct sk_buff *skb); +extern struct ipx_route *ipxrtr_lookup(__u32 net); +extern int ipxrtr_ioctl(unsigned int cmd, void __user *arg); + +#undef IPX_REFCNT_DEBUG +#ifdef IPX_REFCNT_DEBUG +atomic_t ipx_sock_nr; +#endif + +struct ipx_interface *ipx_interfaces_head(void) +{ + struct ipx_interface *rc = NULL; + + if (!list_empty(&ipx_interfaces)) + rc = list_entry(ipx_interfaces.next, + struct ipx_interface, node); + return rc; +} + +static void ipxcfg_set_auto_select(char val) +{ + ipxcfg_auto_select_primary = val; + if (val && !ipx_primary_net) + ipx_primary_net = ipx_interfaces_head(); +} + +static int ipxcfg_get_config_data(struct ipx_config_data __user *arg) +{ + struct 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; +} + +/* + * 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(struct sock *sk) +{ + /* Determine interface with which socket is associated */ + struct ipx_interface *intrfc = ipx_sk(sk)->intrfc; + + if (!intrfc) + goto out; + + ipxitf_hold(intrfc); + spin_lock_bh(&intrfc->if_sklist_lock); + sk_del_node_init(sk); + spin_unlock_bh(&intrfc->if_sklist_lock); + ipxitf_put(intrfc); +out: + return; +} + +static void ipx_destroy_socket(struct sock *sk) +{ + ipx_remove_socket(sk); + skb_queue_purge(&sk->sk_receive_queue); +#ifdef IPX_REFCNT_DEBUG + atomic_dec(&ipx_sock_nr); + printk(KERN_DEBUG "IPX socket %p released, %d are still alive\n", sk, + atomic_read(&ipx_sock_nr)); + if (atomic_read(&sk->sk_refcnt) != 1) + printk(KERN_DEBUG "Destruction sock ipx %p delayed, cnt=%d\n", + sk, atomic_read(&sk->sk_refcnt)); +#endif + sock_put(sk); +} + +/* + * The following code is used to support IPX Interfaces (IPXITF). An + * IPX interface is defined by a physical device and a frame type. + */ + +/* ipxitf_clear_primary_net has to be called with ipx_interfaces_lock held */ + +static void ipxitf_clear_primary_net(void) +{ + ipx_primary_net = NULL; + if (ipxcfg_auto_select_primary) + ipx_primary_net = ipx_interfaces_head(); +} + +static struct ipx_interface *__ipxitf_find_using_phys(struct net_device *dev, + unsigned short datalink) +{ + struct ipx_interface *i; + + list_for_each_entry(i, &ipx_interfaces, node) + if (i->if_dev == dev && i->if_dlink_type == datalink) + goto out; + i = NULL; +out: + return i; +} + +static struct ipx_interface *ipxitf_find_using_phys(struct net_device *dev, + unsigned short datalink) +{ + struct ipx_interface *i; + + spin_lock_bh(&ipx_interfaces_lock); + i = __ipxitf_find_using_phys(dev, datalink); + if (i) + ipxitf_hold(i); + spin_unlock_bh(&ipx_interfaces_lock); + return i; +} + +struct ipx_interface *ipxitf_find_using_net(__u32 net) +{ + struct ipx_interface *i; + + spin_lock_bh(&ipx_interfaces_lock); + if (net) { + list_for_each_entry(i, &ipx_interfaces, node) + if (i->if_netnum == net) + goto hold; + i = NULL; + goto unlock; + } + + i = ipx_primary_net; + if (i) +hold: + ipxitf_hold(i); +unlock: + spin_unlock_bh(&ipx_interfaces_lock); + return i; +} + +/* Sockets are bound to a particular IPX interface. */ +static void ipxitf_insert_socket(struct ipx_interface *intrfc, struct sock *sk) +{ + ipxitf_hold(intrfc); + spin_lock_bh(&intrfc->if_sklist_lock); + ipx_sk(sk)->intrfc = intrfc; + sk_add_node(sk, &intrfc->if_sklist); + spin_unlock_bh(&intrfc->if_sklist_lock); + ipxitf_put(intrfc); +} + +/* caller must hold intrfc->if_sklist_lock */ +static struct sock *__ipxitf_find_socket(struct ipx_interface *intrfc, + unsigned short port) +{ + struct sock *s; + struct hlist_node *node; + + sk_for_each(s, node, &intrfc->if_sklist) + if (ipx_sk(s)->port == port) + goto found; + s = NULL; +found: + return s; +} + +/* caller must hold a reference to intrfc */ +static struct sock *ipxitf_find_socket(struct ipx_interface *intrfc, + unsigned short port) +{ + struct sock *s; + + spin_lock_bh(&intrfc->if_sklist_lock); + s = __ipxitf_find_socket(intrfc, port); + if (s) + sock_hold(s); + spin_unlock_bh(&intrfc->if_sklist_lock); + + return s; +} + +#ifdef CONFIG_IPX_INTERN +static struct sock *ipxitf_find_internal_socket(struct ipx_interface *intrfc, + unsigned char *ipx_node, + unsigned short port) +{ + struct sock *s; + struct hlist_node *node; + + ipxitf_hold(intrfc); + spin_lock_bh(&intrfc->if_sklist_lock); + + sk_for_each(s, node, &intrfc->if_sklist) { + struct ipx_sock *ipxs = ipx_sk(s); + + if (ipxs->port == port && + !memcmp(ipx_node, ipxs->node, IPX_NODE_LEN)) + goto found; + } + s = NULL; +found: + spin_unlock_bh(&intrfc->if_sklist_lock); + ipxitf_put(intrfc); + return s; +} +#endif + +static void __ipxitf_down(struct ipx_interface *intrfc) +{ + struct sock *s; + struct hlist_node *node, *t; + + /* Delete all routes associated with this interface */ + ipxrtr_del_routes(intrfc); + + spin_lock_bh(&intrfc->if_sklist_lock); + /* error sockets */ + sk_for_each_safe(s, node, t, &intrfc->if_sklist) { + struct ipx_sock *ipxs = ipx_sk(s); + + s->sk_err = ENOLINK; + s->sk_error_report(s); + ipxs->intrfc = NULL; + ipxs->port = 0; + sock_set_flag(s, SOCK_ZAPPED); /* Indicates it is no longer bound */ + sk_del_node_init(s); + } + INIT_HLIST_HEAD(&intrfc->if_sklist); + spin_unlock_bh(&intrfc->if_sklist_lock); + + /* remove this interface from list */ + list_del(&intrfc->node); + + /* remove this interface from *special* networks */ + if (intrfc == ipx_primary_net) + ipxitf_clear_primary_net(); + if (intrfc == ipx_internal_net) + ipx_internal_net = NULL; + + if (intrfc->if_dev) + dev_put(intrfc->if_dev); + kfree(intrfc); +} + +void ipxitf_down(struct ipx_interface *intrfc) +{ + spin_lock_bh(&ipx_interfaces_lock); + __ipxitf_down(intrfc); + spin_unlock_bh(&ipx_interfaces_lock); +} + +static __inline__ void __ipxitf_put(struct ipx_interface *intrfc) +{ + if (atomic_dec_and_test(&intrfc->refcnt)) + __ipxitf_down(intrfc); +} + +static int ipxitf_device_event(struct notifier_block *notifier, + unsigned long event, void *ptr) +{ + struct net_device *dev = ptr; + struct ipx_interface *i, *tmp; + + if (event != NETDEV_DOWN && event != NETDEV_UP) + goto out; + + spin_lock_bh(&ipx_interfaces_lock); + list_for_each_entry_safe(i, tmp, &ipx_interfaces, node) + if (i->if_dev == dev) { + if (event == NETDEV_UP) + ipxitf_hold(i); + else + __ipxitf_put(i); + } + spin_unlock_bh(&ipx_interfaces_lock); +out: + return NOTIFY_DONE; +} + + +static __exit void ipxitf_cleanup(void) +{ + struct ipx_interface *i, *tmp; + + spin_lock_bh(&ipx_interfaces_lock); + list_for_each_entry_safe(i, tmp, &ipx_interfaces, node) + __ipxitf_put(i); + spin_unlock_bh(&ipx_interfaces_lock); +} + +static void ipxitf_def_skb_handler(struct sock *sock, struct sk_buff *skb) +{ + if (sock_queue_rcv_skb(sock, skb) < 0) + kfree_skb(skb); +} + +/* + * On input skb->sk is NULL. Nobody is charged for the memory. + */ + +/* caller must hold a reference to intrfc */ + +#ifdef CONFIG_IPX_INTERN +static int ipxitf_demux_socket(struct ipx_interface *intrfc, + struct sk_buff *skb, int copy) +{ + struct ipxhdr *ipx = ipx_hdr(skb); + int is_broadcast = !memcmp(ipx->ipx_dest.node, ipx_broadcast_node, + IPX_NODE_LEN); + struct sock *s; + struct hlist_node *node; + int rc; + + spin_lock_bh(&intrfc->if_sklist_lock); + + sk_for_each(s, node, &intrfc->if_sklist) { + struct ipx_sock *ipxs = ipx_sk(s); + + if (ipxs->port == ipx->ipx_dest.sock && + (is_broadcast || !memcmp(ipx->ipx_dest.node, + ipxs->node, IPX_NODE_LEN))) { + /* We found a socket to which to send */ + struct sk_buff *skb1; + + if (copy) { + skb1 = skb_clone(skb, GFP_ATOMIC); + rc = -ENOMEM; + if (!skb1) + goto out; + } 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; + } + } + + /* skb was solely for us, and we did not make a copy, so free it. */ + if (!copy) + kfree_skb(skb); + + rc = 0; +out: + spin_unlock_bh(&intrfc->if_sklist_lock); + return rc; +} +#else +static struct sock *ncp_connection_hack(struct ipx_interface *intrfc, + struct ipxhdr *ipx) +{ + /* The packet's target is a NCP connection handler. We want to hand it + * to the correct socket directly within the kernel, so that the + * mars_nwe packet distribution process does not have to do it. Here we + * only care about NCP and BURST packets. + * + * You might call this a hack, but believe me, you do not want a + * complete NCP layer in the kernel, and this is VERY fast as well. */ + struct sock *sk = NULL; + int connection = 0; + u8 *ncphdr = (u8 *)(ipx + 1); + + if (*ncphdr == 0x22 && *(ncphdr + 1) == 0x22) /* NCP request */ + connection = (((int) *(ncphdr + 5)) << 8) | (int) *(ncphdr + 3); + else if (*ncphdr == 0x77 && *(ncphdr + 1) == 0x77) /* BURST packet */ + connection = (((int) *(ncphdr + 9)) << 8) | (int) *(ncphdr + 8); + + if (connection) { + struct hlist_node *node; + /* Now we have to look for a special NCP connection handling + * socket. Only these sockets have ipx_ncp_conn != 0, set by + * SIOCIPXNCPCONN. */ + spin_lock_bh(&intrfc->if_sklist_lock); + sk_for_each(sk, node, &intrfc->if_sklist) + if (ipx_sk(sk)->ipx_ncp_conn == connection) { + sock_hold(sk); + goto found; + } + sk = NULL; + found: + spin_unlock_bh(&intrfc->if_sklist_lock); + } + return sk; +} + +static int ipxitf_demux_socket(struct ipx_interface *intrfc, + struct sk_buff *skb, int copy) +{ + struct ipxhdr *ipx = ipx_hdr(skb); + struct sock *sock1 = NULL, *sock2 = NULL; + struct sk_buff *skb1 = NULL, *skb2 = NULL; + int rc; + + if (intrfc == ipx_primary_net && ntohs(ipx->ipx_dest.sock) == 0x451) + sock1 = ncp_connection_hack(intrfc, ipx); + if (!sock1) + /* No special socket found, forward the packet the normal way */ + 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) { + const int dsock = ntohs(ipx->ipx_dest.sock); + + if (dsock == 0x452 || dsock == 0x453 || dsock == 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); + } + + /* + * If there is nothing to do return. The kfree will cancel any charging. + */ + rc = 0; + if (!sock1 && !sock2) { + if (!copy) + kfree_skb(skb); + goto out; + } + + /* + * 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; + + rc = -ENOMEM; + if (!skb1) + goto out_put; + + /* Do we need 2 SKBs? */ + if (sock1 && sock2) + skb2 = skb_clone(skb1, GFP_ATOMIC); + else + skb2 = skb1; + + if (sock1) + ipxitf_def_skb_handler(sock1, skb1); + + if (!skb2) + goto out_put; + + if (sock2) + ipxitf_def_skb_handler(sock2, skb2); + + rc = 0; +out_put: + if (sock1) + sock_put(sock1); + if (sock2) + sock_put(sock2); +out: + return rc; +} +#endif /* CONFIG_IPX_INTERN */ + +static struct sk_buff *ipxitf_adjust_skbuff(struct ipx_interface *intrfc, + struct sk_buff *skb) +{ + struct sk_buff *skb2; + int in_offset = (unsigned char *)ipx_hdr(skb) - 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) { + skb_reserve(skb2, out_offset); + skb2->nh.raw = skb2->h.raw = skb_put(skb2, skb->len); + memcpy(ipx_hdr(skb2), ipx_hdr(skb), skb->len); + memcpy(skb2->cb, skb->cb, sizeof(skb->cb)); + } + kfree_skb(skb); + return skb2; +} + +/* caller must hold a reference to intrfc and the skb has to be unshared */ +int ipxitf_send(struct ipx_interface *intrfc, struct sk_buff *skb, char *node) +{ + struct ipxhdr *ipx = ipx_hdr(skb); + struct net_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; + + ipx->ipx_tctrl = IPX_SKB_CB(skb)->ipx_tctrl; + ipx->ipx_dest.net = IPX_SKB_CB(skb)->ipx_dest_net; + ipx->ipx_source.net = IPX_SKB_CB(skb)->ipx_source_net; + + /* see if we need to include the netnum in the route list */ + if (IPX_SKB_CB(skb)->last_hop.index >= 0) { + u32 *last_hop = (u32 *)(((u8 *) skb->data) + + sizeof(struct ipxhdr) + + IPX_SKB_CB(skb)->last_hop.index * + sizeof(u32)); + *last_hop = IPX_SKB_CB(skb)->last_hop.netnum; + IPX_SKB_CB(skb)->last_hop.index = -1; + } + + /* + * We need to know how many skbuffs it will take to send out this + * packet to avoid unnecessary copies. + */ + + if (!dl || !dev || 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)) { + /* 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)) { + if (!send_to_wire) + skb_orphan(skb); + ipxitf_demux_socket(intrfc, skb, send_to_wire); + if (!send_to_wire) + goto out; + } + } + + /* + * 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 it's a flood or tcpdump + */ + skb = skb_unshare(skb, GFP_ATOMIC); + if (!skb) + goto out; + if (++ipx->ipx_tctrl > ipxcfg_max_hops) + send_to_wire = 0; + } + + if (!send_to_wire) { + kfree_skb(skb); + goto out; + } + + /* Determine the appropriate hardware address */ + addr_len = dev->addr_len; + if (!memcmp(ipx_broadcast_node, node, IPX_NODE_LEN)) + 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) + goto out; + + /* set up data link and physical headers */ + skb->dev = dev; + skb->protocol = htons(ETH_P_IPX); + + /* Send it out */ + dl->request(dl, skb, dest_node); +out: + return 0; +} + +static int ipxitf_add_local_route(struct ipx_interface *intrfc) +{ + return ipxrtr_add_route(intrfc->if_netnum, intrfc, NULL); +} + +static void ipxitf_discover_netnum(struct ipx_interface *intrfc, + struct sk_buff *skb); +static int ipxitf_pprop(struct ipx_interface *intrfc, struct sk_buff *skb); + +static int ipxitf_rcv(struct ipx_interface *intrfc, struct sk_buff *skb) +{ + struct ipxhdr *ipx = ipx_hdr(skb); + int rc = 0; + + ipxitf_hold(intrfc); + + /* See if we should update our network number */ + if (!intrfc->if_netnum) /* net number of intrfc not known yet */ + ipxitf_discover_netnum(intrfc, skb); + + IPX_SKB_CB(skb)->last_hop.index = -1; + if (ipx->ipx_type == IPX_TYPE_PPROP) { + rc = ipxitf_pprop(intrfc, skb); + if (rc) + goto out_free_skb; + } + + /* local processing follows */ + if (!IPX_SKB_CB(skb)->ipx_dest_net) + IPX_SKB_CB(skb)->ipx_dest_net = intrfc->if_netnum; + if (!IPX_SKB_CB(skb)->ipx_source_net) + IPX_SKB_CB(skb)->ipx_source_net = intrfc->if_netnum; + + /* it doesn't make sense to route a pprop packet, there's no meaning + * in the ipx_dest_net for such packets */ + if (ipx->ipx_type != IPX_TYPE_PPROP && + intrfc->if_netnum != IPX_SKB_CB(skb)->ipx_dest_net) { + /* We only route point-to-point packets. */ + if (skb->pkt_type == PACKET_HOST) { + skb = skb_unshare(skb, GFP_ATOMIC); + if (skb) + rc = ipxrtr_route_skb(skb); + goto out_intrfc; + } + + goto out_free_skb; + } + + /* see if we should keep it */ + if (!memcmp(ipx_broadcast_node, ipx->ipx_dest.node, IPX_NODE_LEN) || + !memcmp(intrfc->if_node, ipx->ipx_dest.node, IPX_NODE_LEN)) { + rc = ipxitf_demux_socket(intrfc, skb, 0); + goto out_intrfc; + } + + /* we couldn't pawn it off so unload it */ +out_free_skb: + kfree_skb(skb); +out_intrfc: + ipxitf_put(intrfc); + return rc; +} + +static void ipxitf_discover_netnum(struct ipx_interface *intrfc, + struct sk_buff *skb) +{ + const struct ipx_cb *cb = IPX_SKB_CB(skb); + + /* see if this is an intra packet: source_net == dest_net */ + if (cb->ipx_source_net == cb->ipx_dest_net && cb->ipx_source_net) { + struct ipx_interface *i = + ipxitf_find_using_net(cb->ipx_source_net); + /* 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) { + intrfc->if_netnum = cb->ipx_source_net; + ipxitf_add_local_route(intrfc); + } else { + printk(KERN_WARNING "IPX: Network number collision " + "%lx\n %s %s and %s %s\n", + (unsigned long) htonl(cb->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)); + ipxitf_put(i); + } + } +} + +/** + * ipxitf_pprop - Process packet propagation IPX packet type 0x14, used for + * NetBIOS broadcasts + * @intrfc: IPX interface receiving this packet + * @skb: Received packet + * + * Checks if packet is valid: if its more than %IPX_MAX_PPROP_HOPS hops or if it + * is smaller than a IPX header + the room for %IPX_MAX_PPROP_HOPS hops we drop + * it, not even processing it locally, if it has exact %IPX_MAX_PPROP_HOPS we + * don't broadcast it, but process it locally. See chapter 5 of Novell's "IPX + * RIP and SAP Router Specification", Part Number 107-000029-001. + * + * If it is valid, check if we have pprop broadcasting enabled by the user, + * if not, just return zero for local processing. + * + * If it is enabled check the packet and don't broadcast it if we have already + * seen this packet. + * + * Broadcast: send it to the interfaces that aren't on the packet visited nets + * array, just after the IPX header. + * + * Returns -EINVAL for invalid packets, so that the calling function drops + * the packet without local processing. 0 if packet is to be locally processed. + */ +static int ipxitf_pprop(struct ipx_interface *intrfc, struct sk_buff *skb) +{ + struct ipxhdr *ipx = ipx_hdr(skb); + int i, rc = -EINVAL; + struct ipx_interface *ifcs; + char *c; + u32 *l; + + /* Illegal packet - too many hops or too short */ + /* We decide to throw it away: no broadcasting, no local processing. + * NetBIOS unaware implementations route them as normal packets - + * tctrl <= 15, any data payload... */ + if (IPX_SKB_CB(skb)->ipx_tctrl > IPX_MAX_PPROP_HOPS || + ntohs(ipx->ipx_pktsize) < sizeof(struct ipxhdr) + + IPX_MAX_PPROP_HOPS * sizeof(u32)) + goto out; + /* are we broadcasting this damn thing? */ + rc = 0; + if (!sysctl_ipx_pprop_broadcasting) + goto out; + /* We do broadcast packet on the IPX_MAX_PPROP_HOPS hop, but we + * process it locally. All previous hops broadcasted it, and process it + * locally. */ + if (IPX_SKB_CB(skb)->ipx_tctrl == IPX_MAX_PPROP_HOPS) + goto out; + + c = ((u8 *) ipx) + sizeof(struct ipxhdr); + l = (u32 *) c; + + /* Don't broadcast packet if already seen this net */ + for (i = 0; i < IPX_SKB_CB(skb)->ipx_tctrl; i++) + if (*l++ == intrfc->if_netnum) + goto out; + + /* < IPX_MAX_PPROP_HOPS hops && input interface not in list. Save the + * position where we will insert recvd netnum into list, later on, + * in ipxitf_send */ + IPX_SKB_CB(skb)->last_hop.index = i; + IPX_SKB_CB(skb)->last_hop.netnum = intrfc->if_netnum; + /* xmit on all other interfaces... */ + spin_lock_bh(&ipx_interfaces_lock); + list_for_each_entry(ifcs, &ipx_interfaces, node) { + /* Except unconfigured interfaces */ + if (!ifcs->if_netnum) + continue; + + /* That aren't in the list */ + if (ifcs == intrfc) + continue; + l = (__u32 *) c; + /* don't consider the last entry in the packet list, + * it is our netnum, and it is not there yet */ + for (i = 0; i < IPX_SKB_CB(skb)->ipx_tctrl; i++) + if (ifcs->if_netnum == *l++) + break; + if (i == IPX_SKB_CB(skb)->ipx_tctrl) { + struct sk_buff *s = skb_copy(skb, GFP_ATOMIC); + + if (s) { + IPX_SKB_CB(s)->ipx_dest_net = ifcs->if_netnum; + ipxrtr_route_skb(s); + } + } + } + spin_unlock_bh(&ipx_interfaces_lock); +out: + return rc; +} + +static void ipxitf_insert(struct ipx_interface *intrfc) +{ + spin_lock_bh(&ipx_interfaces_lock); + list_add_tail(&intrfc->node, &ipx_interfaces); + spin_unlock_bh(&ipx_interfaces_lock); + + if (ipxcfg_auto_select_primary && !ipx_primary_net) + ipx_primary_net = intrfc; +} + +static struct ipx_interface *ipxitf_alloc(struct net_device *dev, __u32 netnum, + unsigned short dlink_type, + struct datalink_proto *dlink, + unsigned char internal, + int ipx_offset) +{ + struct ipx_interface *intrfc = kmalloc(sizeof(*intrfc), GFP_ATOMIC); + + if (intrfc) { + intrfc->if_dev = dev; + intrfc->if_netnum = netnum; + intrfc->if_dlink_type = dlink_type; + intrfc->if_dlink = dlink; + intrfc->if_internal = internal; + intrfc->if_ipx_offset = ipx_offset; + intrfc->if_sknum = IPX_MIN_EPHEMERAL_SOCKET; + INIT_HLIST_HEAD(&intrfc->if_sklist); + atomic_set(&intrfc->refcnt, 1); + spin_lock_init(&intrfc->if_sklist_lock); + } + + return intrfc; +} + +static int ipxitf_create_internal(struct ipx_interface_definition *idef) +{ + struct ipx_interface *intrfc; + int rc = -EEXIST; + + /* Only one primary network allowed */ + if (ipx_primary_net) + goto out; + + /* Must have a valid network number */ + rc = -EADDRNOTAVAIL; + if (!idef->ipx_network) + goto out; + intrfc = ipxitf_find_using_net(idef->ipx_network); + rc = -EADDRINUSE; + if (intrfc) { + ipxitf_put(intrfc); + goto out; + } + intrfc = ipxitf_alloc(NULL, idef->ipx_network, 0, NULL, 1, 0); + rc = -EAGAIN; + if (!intrfc) + goto out; + memcpy((char *)&(intrfc->if_node), idef->ipx_node, IPX_NODE_LEN); + ipx_internal_net = ipx_primary_net = intrfc; + ipxitf_hold(intrfc); + ipxitf_insert(intrfc); + + rc = ipxitf_add_local_route(intrfc); + ipxitf_put(intrfc); +out: + return rc; +} + +static int ipx_map_frame_type(unsigned char type) +{ + int rc = 0; + + switch (type) { + case IPX_FRAME_ETHERII: rc = htons(ETH_P_IPX); break; + case IPX_FRAME_8022: rc = htons(ETH_P_802_2); break; + case IPX_FRAME_SNAP: rc = htons(ETH_P_SNAP); break; + case IPX_FRAME_8023: rc = htons(ETH_P_802_3); break; + } + + return rc; +} + +static int ipxitf_create(struct ipx_interface_definition *idef) +{ + struct net_device *dev; + unsigned short dlink_type = 0; + struct datalink_proto *datalink = NULL; + struct ipx_interface *intrfc; + int rc; + + if (idef->ipx_special == IPX_INTERNAL) { + rc = ipxitf_create_internal(idef); + goto out; + } + + rc = -EEXIST; + if (idef->ipx_special == IPX_PRIMARY && ipx_primary_net) + goto out; + + intrfc = ipxitf_find_using_net(idef->ipx_network); + rc = -EADDRINUSE; + if (idef->ipx_network && intrfc) { + ipxitf_put(intrfc); + goto out; + } + + if (intrfc) + ipxitf_put(intrfc); + + dev = dev_get_by_name(idef->ipx_device); + rc = -ENODEV; + if (!dev) + goto out; + + switch (idef->ipx_dlink_type) { + case IPX_FRAME_TR_8022: + printk(KERN_WARNING "IPX frame type 802.2TR is " + "obsolete Use 802.2 instead.\n"); + /* fall through */ + case IPX_FRAME_8022: + dlink_type = htons(ETH_P_802_2); + datalink = p8022_datalink; + break; + case IPX_FRAME_ETHERII: + if (dev->type != ARPHRD_IEEE802) { + dlink_type = htons(ETH_P_IPX); + datalink = pEII_datalink; + break; + } else + printk(KERN_WARNING "IPX frame type EtherII over " + "token-ring is obsolete. Use SNAP " + "instead.\n"); + /* fall through */ + 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: + rc = -EPROTONOSUPPORT; + goto out_dev; + } + + rc = -ENETDOWN; + if (!(dev->flags & IFF_UP)) + goto out_dev; + + /* Check addresses are suitable */ + rc = -EINVAL; + if (dev->addr_len > IPX_NODE_LEN) + goto out_dev; + + intrfc = ipxitf_find_using_phys(dev, dlink_type); + if (!intrfc) { + /* Ok now create */ + intrfc = ipxitf_alloc(dev, idef->ipx_network, dlink_type, + datalink, 0, dev->hard_header_len + + datalink->header_length); + rc = -EAGAIN; + if (!intrfc) + goto out_dev; + /* Setup primary if necessary */ + if (idef->ipx_special == IPX_PRIMARY) + ipx_primary_net = intrfc; + if (!memcmp(idef->ipx_node, "\000\000\000\000\000\000", + IPX_NODE_LEN)) { + memset(intrfc->if_node, 0, IPX_NODE_LEN); + memcpy(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_hold(intrfc); + ipxitf_insert(intrfc); + } + + + /* If the network number is known, add a route */ + rc = 0; + if (!intrfc->if_netnum) + goto out_intrfc; + + rc = ipxitf_add_local_route(intrfc); +out_intrfc: + ipxitf_put(intrfc); + goto out; +out_dev: + dev_put(dev); +out: + return rc; +} + +static int ipxitf_delete(struct ipx_interface_definition *idef) +{ + struct net_device *dev = NULL; + unsigned short dlink_type = 0; + struct ipx_interface *intrfc; + int rc = 0; + + spin_lock_bh(&ipx_interfaces_lock); + if (idef->ipx_special == IPX_INTERNAL) { + if (ipx_internal_net) { + __ipxitf_put(ipx_internal_net); + goto out; + } + rc = -ENOENT; + goto out; + } + + dlink_type = ipx_map_frame_type(idef->ipx_dlink_type); + rc = -EPROTONOSUPPORT; + if (!dlink_type) + goto out; + + dev = __dev_get_by_name(idef->ipx_device); + rc = -ENODEV; + if (!dev) + goto out; + + intrfc = __ipxitf_find_using_phys(dev, dlink_type); + rc = -EINVAL; + if (!intrfc) + goto out; + __ipxitf_put(intrfc); + + rc = 0; +out: + spin_unlock_bh(&ipx_interfaces_lock); + return rc; +} + +static struct ipx_interface *ipxitf_auto_create(struct net_device *dev, + unsigned short dlink_type) +{ + struct ipx_interface *intrfc = NULL; + struct datalink_proto *datalink; + + if (!dev) + goto out; + + /* Check addresses are suitable */ + if (dev->addr_len > IPX_NODE_LEN) + goto out; + + 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: goto out; + } + + intrfc = ipxitf_alloc(dev, 0, dlink_type, datalink, 0, + dev->hard_header_len + datalink->header_length); + + if (intrfc) { + 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); + spin_lock_init(&intrfc->if_sklist_lock); + atomic_set(&intrfc->refcnt, 1); + ipxitf_insert(intrfc); + dev_hold(dev); + } + +out: + return intrfc; +} + +static int ipxitf_ioctl(unsigned int cmd, void __user *arg) +{ + int rc = -EINVAL; + struct ifreq ifr; + int val; + + switch (cmd) { + case SIOCSIFADDR: { + struct sockaddr_ipx *sipx; + struct ipx_interface_definition f; + + rc = -EFAULT; + if (copy_from_user(&ifr, arg, sizeof(ifr))) + break; + sipx = (struct sockaddr_ipx *)&ifr.ifr_addr; + rc = -EINVAL; + if (sipx->sipx_family != AF_IPX) + break; + 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) + rc = ipxitf_delete(&f); + else + rc = ipxitf_create(&f); + break; + } + case SIOCGIFADDR: { + struct sockaddr_ipx *sipx; + struct ipx_interface *ipxif; + struct net_device *dev; + + rc = -EFAULT; + if (copy_from_user(&ifr, arg, sizeof(ifr))) + break; + sipx = (struct sockaddr_ipx *)&ifr.ifr_addr; + dev = __dev_get_by_name(ifr.ifr_name); + rc = -ENODEV; + if (!dev) + break; + ipxif = ipxitf_find_using_phys(dev, + ipx_map_frame_type(sipx->sipx_type)); + rc = -EADDRNOTAVAIL; + if (!ipxif) + break; + + sip |