/* * DDP: An implementation of the AppleTalk DDP protocol for * Ethernet 'ELAP'. * * Alan Cox * * With more than a little assistance from * * Wesley Craig * * Fixes: * Michael Callahan : Made routing work * Wesley Craig : Fix probing to listen to a * passed node id. * Alan Cox : Added send/recvmsg support * Alan Cox : Moved at. to protinfo in * socket. * Alan Cox : Added firewall hooks. * Alan Cox : Supports new ARPHRD_LOOPBACK * Christer Weinigel : Routing and /proc fixes. * Bradford Johnson : LocalTalk. * Tom Dyas : Module support. * Alan Cox : Hooks for PPP (based on the * LocalTalk hook). * Alan Cox : Posix bits * Alan Cox/Mike Freeman : Possible fix to NBP problems * Bradford Johnson : IP-over-DDP (experimental) * Jay Schulist : Moved IP-over-DDP to its own * driver file. (ipddp.c & ipddp.h) * Jay Schulist : Made work as module with * AppleTalk drivers, cleaned it. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. * */ #include #if defined(CONFIG_ATALK) || defined(CONFIG_ATALK_MODULE) #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* For TIOCOUTQ/INQ */ #include #include #include #include #include #include #include #include #include #include #include #undef APPLETALK_DEBUG #ifdef APPLETALK_DEBUG #define DPRINT(x) print(x) #else #define DPRINT(x) #endif /* APPLETALK_DEBUG */ #ifdef CONFIG_SYSCTL extern inline void atalk_register_sysctl(void); extern inline void atalk_unregister_sysctl(void); #endif /* CONFIG_SYSCTL */ struct datalink_proto *ddp_dl, *aarp_dl; static struct proto_ops atalk_dgram_ops; #define min(a,b) (((a)<(b))?(a):(b)) /**************************************************************************\ * * * Handlers for the socket list. * * * \**************************************************************************/ static struct sock *atalk_socket_list = NULL; /* * 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. */ extern inline void atalk_remove_socket(struct sock *sk) { sklist_remove_socket(&atalk_socket_list,sk); } extern inline void atalk_insert_socket(struct sock *sk) { sklist_insert_socket(&atalk_socket_list,sk); } static struct sock *atalk_search_socket(struct sockaddr_at *to, struct atalk_iface *atif) { struct sock *s; for(s = atalk_socket_list; s != NULL; s = s->next) { if(to->sat_port != s->protinfo.af_at.src_port) { continue; } if(to->sat_addr.s_net == 0 && to->sat_addr.s_node == ATADDR_BCAST && s->protinfo.af_at.src_net == atif->address.s_net) { break; } if(to->sat_addr.s_net == s->protinfo.af_at.src_net && (to->sat_addr.s_node == s->protinfo.af_at.src_node || to->sat_addr.s_node == ATADDR_BCAST || to->sat_addr.s_node == ATADDR_ANYNODE)) { break; } /* XXXX.0 */ } return (s); } /* * Find a socket in the list. */ static struct sock *atalk_find_socket(struct sockaddr_at *sat) { struct sock *s; for(s = atalk_socket_list; s != NULL; s = s->next) { if(s->protinfo.af_at.src_net != sat->sat_addr.s_net) { continue; } if(s->protinfo.af_at.src_node != sat->sat_addr.s_node) { continue; } if(s->protinfo.af_at.src_port != sat->sat_port) { continue; } break; } return (s); } extern inline void atalk_destroy_socket(struct sock *sk) { sklist_destroy_socket(&atalk_socket_list, sk); MOD_DEC_USE_COUNT; } /* * Called from proc fs */ int atalk_get_info(char *buffer, char **start, off_t offset, int length, int dummy) { struct sock *s; int len=0; off_t pos=0; off_t begin=0; /* * Output the AppleTalk data for the /proc filesystem. */ len += sprintf(buffer,"Type local_addr remote_addr tx_queue rx_queue st uid\n"); for(s = atalk_socket_list; s != NULL; s = s->next) { len += sprintf(buffer+len,"%02X ", s->type); len += sprintf(buffer+len,"%04X:%02X:%02X ", ntohs(s->protinfo.af_at.src_net), s->protinfo.af_at.src_node, s->protinfo.af_at.src_port); len += sprintf(buffer+len,"%04X:%02X:%02X ", ntohs(s->protinfo.af_at.dest_net), s->protinfo.af_at.dest_node, s->protinfo.af_at.dest_port); len += sprintf(buffer+len,"%08X:%08X ", atomic_read(&s->wmem_alloc), atomic_read(&s->rmem_alloc)); len += sprintf(buffer+len,"%02X %d\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); } /**************************************************************************\ * * * Routing tables for the AppleTalk socket layer. * * * \**************************************************************************/ static struct atalk_route *atalk_router_list = NULL; static struct atalk_iface *atalk_iface_list = NULL; static struct atalk_route atrtr_default; /* For probing devices or in a routerless network */ /* * AppleTalk interface control */ /* * Drop a device. Doesn't drop any of its routes - that is the caller's * problem. Called when we down the interface or delete the address. */ static void atif_drop_device(struct device *dev) { struct atalk_iface **iface = &atalk_iface_list; struct atalk_iface *tmp; while((tmp = *iface) != NULL) { if(tmp->dev == dev) { *iface = tmp->next; kfree_s(tmp, sizeof(struct atalk_iface)); dev->atalk_ptr=NULL; MOD_DEC_USE_COUNT; } else iface = &tmp->next; } } static struct atalk_iface *atif_add_device(struct device *dev, struct at_addr *sa) { struct atalk_iface *iface = (struct atalk_iface *) kmalloc(sizeof(*iface), GFP_KERNEL); unsigned long flags; if(iface==NULL) return (NULL); iface->dev=dev; dev->atalk_ptr=iface; iface->address= *sa; iface->status=0; save_flags(flags); cli(); iface->next=atalk_iface_list; atalk_iface_list=iface; restore_flags(flags); MOD_INC_USE_COUNT; return (iface); } /* * Probe a Phase 1 device or a device that requires its Net:Node to * be set via an ioctl. */ void atif_send_probe_phase1(struct atalk_iface *iface) { struct ifreq atreq; struct sockaddr_at *sa = (struct sockaddr_at *)&atreq.ifr_addr; sa->sat_addr.s_node = iface->address.s_node; sa->sat_addr.s_net = ntohs(iface->address.s_net); /* We pass the Net:Node to the drivers/cards by a Device ioctl. */ if(!(iface->dev->do_ioctl(iface->dev, &atreq, SIOCSIFADDR))) { (void)iface->dev->do_ioctl(iface->dev, &atreq, SIOCGIFADDR); if((iface->address.s_net != htons(sa->sat_addr.s_net)) || (iface->address.s_node != sa->sat_addr.s_node)) iface->status |= ATIF_PROBE_FAIL; iface->address.s_net = htons(sa->sat_addr.s_net); iface->address.s_node = sa->sat_addr.s_node; } return; } /* * Perform phase 2 AARP probing on our tentative address. */ static int atif_probe_device(struct atalk_iface *atif) { int netrange=ntohs(atif->nets.nr_lastnet)-ntohs(atif->nets.nr_firstnet)+1; int probe_net=ntohs(atif->address.s_net); int probe_node=atif->address.s_node; int ct, netct, nodect; /* * Offset the network we start probing with. */ if(probe_net == ATADDR_ANYNET) { if(!netrange) probe_net = ntohs(atif->nets.nr_firstnet); else probe_net = ntohs(atif->nets.nr_firstnet) + (jiffies%netrange); } if(probe_node == ATADDR_ANYNODE) probe_node = jiffies&0xFF; /* * Scan the networks. */ for(netct = 0; netct <= netrange; netct++) { /* * Sweep the available nodes from a given start. */ atif->address.s_net = htons(probe_net); for(nodect = 0; nodect < 256; nodect++) { atif->address.s_node = ((nodect+probe_node) & 0xFF); if(atif->address.s_node > 0 && atif->address.s_node<254) { /* * Probe a proposed address. */ if(atif->dev->type == ARPHRD_LOCALTLK || atif->dev->type == ARPHRD_PPP) atif_send_probe_phase1(atif); else { for(ct = 0; ct < AARP_RETRANSMIT_LIMIT; ct++) { aarp_send_probe(atif->dev, &atif->address); /* * Defer 1/10th */ current->timeout = jiffies + (HZ/10); current->state = TASK_INTERRUPTIBLE; schedule(); if(atif->status & ATIF_PROBE_FAIL) break; } } if(!(atif->status & ATIF_PROBE_FAIL)) return (0); } atif->status &= ~ATIF_PROBE_FAIL; } probe_net++; if(probe_net > ntohs(atif->nets.nr_lastnet)) probe_net = ntohs(atif->nets.nr_firstnet); } return (-EADDRINUSE); /* Network is full... */ } struct at_addr *atalk_find_dev_addr(struct device *dev) { struct atalk_iface *iface=dev->atalk_ptr; if(iface) return (&iface->address); return (NULL); } static struct at_addr *atalk_find_primary(void) { struct atalk_iface *iface; struct atalk_iface *fiface = NULL; /* * Return a point-to-point interface only if * there is no non-ptp interface available. */ for(iface=atalk_iface_list; iface != NULL; iface=iface->next) { if(!fiface && !(iface->dev->flags & IFF_LOOPBACK)) fiface=iface; if(!(iface->dev->flags & (IFF_LOOPBACK | IFF_POINTOPOINT))) return (&iface->address); } if(fiface) return (&fiface->address); if(atalk_iface_list != NULL) return (&atalk_iface_list->address); else return (NULL); } /* * Find a match for 'any network' - ie any of our interfaces with that * node number will do just nicely. */ static struct atalk_iface *atalk_find_anynet(int node, struct device *dev) { struct atalk_iface *iface=dev->atalk_ptr; if(iface==NULL || (iface->status & ATIF_PROBE)) return (NULL); if(node == ATADDR_BCAST || iface->address.s_node == node || node == ATADDR_ANYNODE) return (iface); return (NULL); } /* * Find a match for a specific network:node pair */ static struct atalk_iface *atalk_find_interface(int net, int node) { struct atalk_iface *iface; for(iface=atalk_iface_list; iface != NULL; iface=iface->next) { if((node==ATADDR_BCAST || node==ATADDR_ANYNODE || iface->address.s_node==node) && iface->address.s_net==net && !(iface->status & ATIF_PROBE)) return (iface); } return (NULL); } /* * Find a route for an AppleTalk packet. This ought to get cached in * the socket (later on...). We know about host routes and the fact * that a route must be direct to broadcast. */ static struct atalk_route *atrtr_find(struct at_addr *target) { struct atalk_route *r; for(r=atalk_router_list; r != NULL; r=r->next) { if(!(r->flags & RTF_UP)) continue; if(r->target.s_net == target->s_net) { if(!(r->flags&RTF_HOST) || r->target.s_node == target->s_node) return (r); } } if(atrtr_default.dev) return (&atrtr_default); return (NULL); } /* * Given an AppleTalk network, find the device to use. This can be * a simple lookup. */ struct device *atrtr_get_dev(struct at_addr *sa) { struct atalk_route *atr=atrtr_find(sa); if(atr == NULL) return (NULL); else return (atr->dev); } /* * Set up a default router. */ static void atrtr_set_default(struct device *dev) { atrtr_default.dev = dev; atrtr_default.flags = RTF_UP; atrtr_default.gateway.s_net = htons(0); atrtr_default.gateway.s_node = 0; } /* * Add a router. Basically make sure it looks valid and stuff the * entry in the list. While it uses netranges we always set them to one * entry to work like netatalk. */ static int atrtr_create(struct rtentry *r, struct device *devhint) { struct sockaddr_at *ta=(struct sockaddr_at *)&r->rt_dst; struct sockaddr_at *ga=(struct sockaddr_at *)&r->rt_gateway; struct atalk_route *rt; struct atalk_iface *iface, *riface; unsigned long flags; save_flags(flags); /* * Fixme: Raise/Lower a routing change semaphore for these * operations. */ /* * Validate the request */ if(ta->sat_family != AF_APPLETALK) return (-EINVAL); if(devhint == NULL && ga->sat_family != AF_APPLETALK) return (-EINVAL); /* * Now walk the routing table and make our decisions. */ for(rt=atalk_router_list; rt!=NULL; rt=rt->next) { if(r->rt_flags != rt->flags) continue; if(ta->sat_addr.s_net == rt->target.s_net) { if(!(rt->flags & RTF_HOST)) break; if(ta->sat_addr.s_node == rt->target.s_node) break; } } if(devhint == NULL) { for(riface = NULL, iface = atalk_iface_list; iface; iface = iface->next) { if(riface == NULL && ntohs(ga->sat_addr.s_net) >= ntohs(iface->nets.nr_firstnet) && ntohs(ga->sat_addr.s_net) <= ntohs(iface->nets.nr_lastnet)) { riface = iface; } if(ga->sat_addr.s_net == iface->address.s_net && ga->sat_addr.s_node == iface->address.s_node) riface = iface; } if(riface == NULL) return (-ENETUNREACH); devhint = riface->dev; } if(rt == NULL) { rt = (struct atalk_route *)kmalloc(sizeof(struct atalk_route), GFP_KERNEL); if(rt == NULL) return (-ENOBUFS); cli(); rt->next = atalk_router_list; atalk_router_list = rt; } /* * Fill in the routing entry. */ rt->target = ta->sat_addr; rt->dev = devhint; rt->flags = r->rt_flags; rt->gateway = ga->sat_addr; restore_flags(flags); return (0); } /* * Delete a route. Find it and discard it. */ static int atrtr_delete( struct at_addr *addr ) { struct atalk_route **r = &atalk_router_list; struct atalk_route *tmp; while((tmp = *r) != NULL) { if(tmp->target.s_net == addr->s_net && (!(tmp->flags&RTF_GATEWAY) || tmp->target.s_node == addr->s_node)) { *r = tmp->next; kfree_s(tmp, sizeof(struct atalk_route)); return (0); } r = &tmp->next; } return (-ENOENT); } /* * Called when a device is downed. Just throw away any routes * via it. */ void atrtr_device_down(struct device *dev) { struct atalk_route **r = &atalk_router_list; struct atalk_route *tmp; while((tmp = *r) != NULL) { if(tmp->dev == dev) { *r = tmp->next; kfree_s(tmp, sizeof(struct atalk_route)); } else r = &tmp->next; } if(atrtr_default.dev == dev) atrtr_set_default(NULL); } /* * Actually down the interface. */ static inline void atalk_dev_down(struct device *dev) { atrtr_device_down(dev); /* Remove all routes for the device */ aarp_device_down(dev); /* Remove AARP entries for the device */ atif_drop_device(dev); /* Remove the device */ } /* * A device event has occurred. Watch for devices going down and * delete our use of them (iface and route). */ static int ddp_device_event(struct notifier_block *this, unsigned long event, void *ptr) { if(event == NETDEV_DOWN) { /* Discard any use of this */ atalk_dev_down((struct device *) ptr); } return (NOTIFY_DONE); } /* * ioctl calls. Shouldn't even need touching. */ /* * Device configuration ioctl calls. */ int atif_ioctl(int cmd, void *arg) { struct ifreq atreq; static char aarp_mcast[6] = {0x09, 0x00, 0x00, 0xFF, 0xFF, 0xFF}; struct netrange *nr; struct sockaddr_at *sa; struct device *dev; struct atalk_iface *atif; int ct; int limit; struct rtentry rtdef; if(copy_from_user(&atreq,arg,sizeof(atreq))) return (-EFAULT); if((dev = dev_get(atreq.ifr_name)) == NULL) return (-ENODEV); sa=(struct sockaddr_at*)&atreq.ifr_addr; atif=atalk_find_dev(dev); switch(cmd) { case SIOCSIFADDR: if(!capable(CAP_NET_ADMIN)) return (-EPERM); if(sa->sat_family != AF_APPLETALK) return (-EINVAL); if(dev->type != ARPHRD_ETHER && dev->type != ARPHRD_LOOPBACK && dev->type != ARPHRD_LOCALTLK && dev->type != ARPHRD_PPP) return (-EPROTONOSUPPORT); nr=(struct netrange *)&sa->sat_zero[0]; /* * Phase 1 is fine on LocalTalk but we don't do * EtherTalk phase 1. Anyone wanting to add it go ahead. */ if(dev->type == ARPHRD_ETHER && nr->nr_phase != 2) return (-EPROTONOSUPPORT); if(sa->sat_addr.s_node == ATADDR_BCAST || sa->sat_addr.s_node == 254) return (-EINVAL); if(atif) { /* * Already setting address. */ if(atif->status&ATIF_PROBE) return (-EBUSY); atif->address.s_net = sa->sat_addr.s_net; atif->address.s_node = sa->sat_addr.s_node; atrtr_device_down(dev); /* Flush old routes */ } else { atif=atif_add_device(dev, &sa->sat_addr); if (atif == NULL) return (-ENOMEM); } atif->nets= *nr; /* * Check if the chosen address is used. If so we * error and atalkd will try another. */ if(!(dev->flags & IFF_LOOPBACK) && atif_probe_device(atif) < 0) { atif_drop_device(dev); return (-EADDRINUSE); } /* * Hey it worked - add the direct routes. */ sa = (struct sockaddr_at *)&rtdef.rt_gateway; sa->sat_family = AF_APPLETALK; sa->sat_addr.s_net = atif->address.s_net; sa->sat_addr.s_node = atif->address.s_node; sa = (struct sockaddr_at *)&rtdef.rt_dst; rtdef.rt_flags = RTF_UP; sa->sat_family = AF_APPLETALK; sa->sat_addr.s_node = ATADDR_ANYNODE; if(dev->flags & IFF_LOOPBACK) rtdef.rt_flags |= RTF_HOST; /* * Routerless initial state. */ if(nr->nr_firstnet == htons(0) && nr->nr_lastnet == htons(0xFFFE)) { sa->sat_addr.s_net = atif->address.s_net; atrtr_create(&rtdef, dev); atrtr_set_default(dev); } else { limit = ntohs(nr->nr_lastnet); if(limit - ntohs(nr->nr_firstnet) > 256) { printk(KERN_WARNING "Too many routes/iface.\n"); return (-EINVAL); } for(ct=ntohs(nr->nr_firstnet);ct<=limit;ct++) { sa->sat_addr.s_net = htons(ct); atrtr_create(&rtdef, dev); } } dev_mc_add(dev, aarp_mcast, 6, 1); return (0); case SIOCGIFADDR: if(atif == NULL) return (-EADDRNOTAVAIL); ((struct sockaddr_at *)(&atreq.ifr_addr))->sat_family=AF_APPLETALK; ((struct sockaddr_at *)(&atreq.ifr_addr))->sat_addr=atif->address; break; case SIOCGIFBRDADDR: if(atif == NULL) return (-EADDRNOTAVAIL); ((struct sockaddr_at *)(&atreq.ifr_addr))->sat_family=AF_APPLETALK; ((struct sockaddr_at *)(&atreq.ifr_addr))->sat_addr.s_net=atif->address.s_net; ((struct sockaddr_at *)(&atreq.ifr_addr))->sat_addr.s_node=ATADDR_BCAST; break; case SIOCATALKDIFADDR: case SIOCDIFADDR: if(!capable(CAP_NET_ADMIN)) return (-EPERM); if(sa->sat_family != AF_APPLETALK) return (-EINVAL); atalk_dev_down(dev); break; } if(copy_to_user(arg, &atreq, sizeof(atreq))) return (-EFAULT); return (0); } /* * Routing ioctl() calls */ static int atrtr_ioctl(unsigned int cmd, void *arg) { struct rtentry rt; if(copy_from_user(&rt, arg, sizeof(rt))) return (-EFAULT); switch(cmd) { case SIOCDELRT: if(rt.rt_dst.sa_family != AF_APPLETALK) return (-EINVAL); return (atrtr_delete(&((struct sockaddr_at *)&rt.rt_dst)->sat_addr)); case SIOCADDRT: return (atrtr_create(&rt, NULL)); default: return (-EINVAL); } } /* Called from proc fs - just make it print the ifaces neatly */ int atalk_if_get_info(char *buffer, char **start, off_t offset, int length, int dummy) { struct atalk_iface *iface; int len=0; off_t pos=0; off_t begin=0; len += sprintf(buffer,"Interface Address Networks Status\n"); for(iface = atalk_iface_list; iface != NULL; iface = iface->next) { len += sprintf(buffer+len,"%-16s %04X:%02X %04X-%04X %d\n", iface->dev->name, ntohs(iface->address.s_net), iface->address.s_node, ntohs(iface->nets.nr_firstnet), ntohs(iface->nets.nr_lastnet), iface->status); 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); } /* Called from proc fs - just make it print the routes neatly */ int atalk_rt_get_info(char *buffer, char **start, off_t offset, int length, int dummy) { struct atalk_route *rt; int len=0; off_t pos=0; off_t begin=0; len += sprintf(buffer,"Target Router Flags Dev\n"); if(atrtr_default.dev) { rt = &atrtr_default; len += sprintf(buffer+len,"Default %04X:%02X %-4d %s\n", ntohs(rt->gateway.s_net), rt->gateway.s_node, rt->flags, rt->dev->name); } for(rt = atalk_router_list; rt != NULL; rt = rt->next) { len += sprintf(buffer+len,"%04X:%02X %04X:%02X %-4d %s\n", ntohs(rt->target.s_net),rt->target.s_node, ntohs(rt->gateway.s_net), rt->gateway.s_node, rt->flags, rt->dev->name); 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 * * AppleTalk socket object. * * * \**************************************************************************/ /* * Checksum: This is 'optional'. It's quite likely also a good * candidate for assembler hackery 8) */ unsigned short atalk_checksum(struct ddpehdr *ddp, int len) { unsigned long sum=0; /* Assume unsigned long is >16 bits */ unsigned char *data=(unsigned char *)ddp; len -= 4; /* skip header 4 bytes */ data += 4; /* This ought to be unwrapped neatly. I'll trust gcc for now */ while(len--) { sum += *data; sum <<= 1; if(sum & 0x10000) { sum++; sum &= 0xFFFF; } data++; } if(sum) return htons((unsigned short)sum); return 0xFFFF; /* Use 0xFFFF for 0. 0 itself means none */ } /* * Create a socket. Initialise the socket, blank the addresses * set the state. */ static int atalk_create(struct socket *sock, int protocol) { struct sock *sk; sk = sk_alloc(PF_APPLETALK, GFP_KERNEL, 1); if(sk == NULL) return (-ENOMEM); switch(sock->type) { /* * We permit SOCK_DGRAM and RAW is an extension. It is * trivial to do and gives you the full ELAP frame. * Should be handy for CAP 8) */ case SOCK_RAW: case SOCK_DGRAM: sock->ops = &atalk_dgram_ops; break; default: sk_free((void *)sk); return (-ESOCKTNOSUPPORT); } MOD_INC_USE_COUNT; sock_init_data(sock,sk); sk->destruct = NULL; /* Checksums on by default */ sk->mtu = DDP_MAXSZ; sk->zapped = 1; return (0); } /* * Free a socket. No work needed */ static int atalk_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; atalk_destroy_socket(sk); return (0); } /* * Pick a source address if one is not given. Just return * an error if not supportable. */ static int atalk_pick_port(struct sockaddr_at *sat) { for(sat->sat_port = ATPORT_RESERVED; sat->sat_port < ATPORT_LAST; sat->sat_port++) { if(atalk_find_socket(sat) == NULL) return sat->sat_port; } return (-EBUSY); } static int atalk_autobind(struct sock *sk) { struct at_addr *ap = atalk_find_primary(); struct sockaddr_at sat; int n; if(ap == NULL || ap->s_net == htons(ATADDR_ANYNET)) return (-EADDRNOTAVAIL); sk->protinfo.af_at.src_net = sat.sat_addr.s_net = ap->s_net; sk->protinfo.af_at.src_node = sat.sat_addr.s_node = ap->s_node; if((n = atalk_pick_port(&sat)) < 0) return (n); sk->protinfo.af_at.src_port = n; atalk_insert_socket(sk); sk->zapped = 0; return (0); } /* * Set the address 'our end' of the connection. */ static int atalk_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len) { struct sock *sk; struct sockaddr_at *addr=(struct sockaddr_at *)uaddr; sk = sock->sk; if(sk->zapped == 0) return (-EINVAL); if(addr_len != sizeof(struct sockaddr_at)) return (-EINVAL); if(addr->sat_family != AF_APPLETALK) return (-EAFNOSUPPORT); if(addr->sat_addr.s_net == htons(ATADDR_ANYNET)) { struct at_addr *ap=atalk_find_primary(); if(ap == NULL) return (-EADDRNOTAVAIL); sk->protinfo.af_at.src_net = addr->sat_addr.s_net = ap->s_net; sk->protinfo.af_at.src_node = addr->sat_addr.s_node= ap->s_node; } else { if(atalk_find_interface(addr->sat_addr.s_net, addr->sat_addr.s_node) == NULL) return (-EADDRNOTAVAIL); sk->protinfo.af_at.src_net = addr->sat_addr.s_net; sk->protinfo.af_at.src_node = addr->sat_addr.s_node; } if(addr->sat_port == ATADDR_ANYPORT) { int n = atalk_pick_port(addr); if(n < 0) return (n); sk->protinfo.af_at.src_port = addr->sat_port = n; } else sk->protinfo.af_at.src_port = addr->sat_port; if(atalk_find_socket(addr) != NULL) return (-EADDRINUSE); atalk_insert_socket(sk); sk->zapped = 0; return (0); } /* * Set the address we talk to. */ static int atalk_connect(struct socket *sock, struct sockaddr *uaddr, int addr_len, int flags) { struct sock *sk=sock->sk; struct sockaddr_at *addr; sk->state = TCP_CLOSE; sock->state = SS_UNCONNECTED; if(addr_len != sizeof(*addr)) return (-EINVAL); addr = (struct sockaddr_at *)uaddr; if(addr->sat_family != AF_APPLETALK) return (-EAFNOSUPPORT); if(addr->sat_addr.s_node == ATADDR_BCAST && !sk->broadcast) { #if 1 printk(KERN_WARNING "%s is broken and did not set SO_BROADCAST. It will break when 2.2 is released.\n", current->comm); #else return (-EACCES); #endif } if(sk->zapped) { if(atalk_autobind(sk) < 0) return (-EBUSY); } if(atrtr_get_dev(&addr->sat_addr) == NULL) return (-ENETUNREACH); sk->protinfo.af_at.dest_port = addr->sat_port; sk->protinfo.af_at.dest_net = addr->sat_addr.s_net; sk->protinfo.af_at.dest_node = addr->sat_addr.s_node; sock->state = SS_CONNECTED; sk->state = TCP_ESTABLISHED; return (0); } /* * Not relevant */ static int atalk_accept(struct socket *sock, struct socket *newsock, int flags) { if(newsock->sk) { sk_free(newsock->sk); MOD_DEC_USE_COUNT; } return (-EOPNOTSUPP); } /* * Find the name of an AppleTalk socket. Just copy the right * fields into the sockaddr. */ static int atalk_getname(struct socket *sock, struct sockaddr *uaddr, int *uaddr_len, int peer) { struct sockaddr_at sat; struct sock *sk; sk = sock->sk; if(sk->zapped) { if(atalk_autobind(sk) < 0) return (-ENOBUFS); } *uaddr_len = sizeof(struct sockaddr_at); if(peer) { if(sk->state != TCP_ESTABLISHED) return (-ENOTCONN); sat.sat_addr.s_net = sk->protinfo.af_at.dest_net; sat.sat_addr.s_node = sk->protinfo.af_at.dest_node; sat.sat_port = sk->protinfo.af_at.dest_port; } else { sat.sat_addr.s_net = sk->protinfo.af_at.src_net; sat.sat_addr.s_node = sk->protinfo.af_at.src_node; sat.sat_port = sk->protinfo.af_at.src_port; } sat.sat_family = AF_APPLETALK; memcpy(uaddr, &sat, sizeof(sat)); return (0); } /* * Receive a packet (in skb) from device dev. This has come from the SNAP * decoder, and on entry skb->h.raw is the DDP header, skb->len is the DDP * header, skb->len is the DDP length. The physical headers have been * extracted. PPP should probably pass frames marked as for this layer. * [ie ARPHRD_ETHERTALK] */ static int atalk_rcv(struct sk_buff *skb, struct device *dev, struct packet_type *pt) { struct sock *sock; struct ddpehdr *ddp=(void *)skb->h.raw; struct atalk_iface *atif; struct sockaddr_at tosat; int origlen; struct ddpebits ddphv; /* Size check */ if(skb->len < sizeof(*ddp)) { kfree_skb(skb); return (0); } /* * Fix up the length field [Ok this is horrible but otherwise * I end up with unions of bit fields and messy bit field order * compiler/endian dependencies..] * * FIXME: This is a write to a shared object. Granted it * happens to be safe BUT.. (Its safe as user space will not * run until we put it back) */ *((__u16 *)&ddphv) = ntohs(*((__u16 *)ddp)); /* * Trim buffer in case of stray trailing data */ origlen = skb->len; skb_trim(skb, min(skb->len, ddphv.deh_len)); /* * Size check to see if ddp->deh_len was crap * (Otherwise we'll detonate most spectacularly * in the middle of recvmsg()). */ if(skb->len < sizeof(*ddp)) { kfree_skb(skb); return (0); } /* * Any checksums. Note we don't do htons() on this == is assumed to be * valid for net byte orders all over the networking code... */ if(ddp->deh_sum && atalk_checksum(ddp, ddphv.deh_len) != ddp->deh_sum) { /* Not a valid AppleTalk frame - dustbin time */ kfree_skb(skb); return (0); } if(call_in_firewall(PF_APPLETALK, skb->dev, ddp, NULL,&skb)!=FW_ACCEPT) { kfree_skb(skb); return (0); } /* Check the packet is aimed at us */ if(ddp->deh_dnet == 0) /* Net 0 is 'this network' */ atif = atalk_find_anynet(ddp->deh_dnode, dev); else atif = atalk_find_interface(ddp->deh_dnet, ddp->deh_dnode); /* * Not ours, so we route the packet via the correct AppleTalk interface. */ if(atif == NULL) { struct atalk_route *rt; struct at_addr ta; /* * Don't route multicast, etc., packets, or packets * sent to "this network" */ if (skb->pkt_type != PACKET_HOST || ddp->deh_dnet == 0) { kfree_skb(skb); return (0); } /* * Check firewall allows this routing */ if(call_fw_firewall(PF_APPLETALK, skb->dev, ddp, NULL, &skb) != FW_ACCEPT) { kfree_skb(skb); return (0); } ta.s_net = ddp->deh_dnet; ta.s_node = ddp->deh_dnode; /* Route the packet */ rt = atrtr_find(&ta); if(rt == NULL || ddphv.deh_hops == DDP_MAXHOPS) { kfree_skb(skb); return (0); } ddphv.deh_hops++; /* * Route goes through another gateway, so * set the target to the gateway instead. */ if(rt->flags & RTF_GATEWAY) { ta.s_net = rt->gateway.s_net; ta.s_node = rt->gateway.s_node; } /* Fix up skb->len field */ skb_trim(skb, min(origlen, rt->dev->hard_header_len + ddp_dl->header_length + ddphv.deh_len)); /* Mend the byte order */ *((__u16 *)ddp) = ntohs(*((__u16 *)&ddphv)); /* * Send the buffer onwards * * Now we must always be careful. If it's come from * LocalTalk to EtherTalk it might not fit * * Order matters here: If a packet has to be copied * to make a new headroom (rare hopefully) then it * won't need unsharing. * * Note. ddp-> becomes invalid at the realloc. */ if(skb_headroom(skb) < 22) /* 22 bytes - 12 ether, 2 len, 3 802.2 5 snap */ skb = skb_realloc_headroom(skb, 32); else skb = skb_unshare(skb, GFP_ATOMIC); /* * If the buffer didn't vanish into the lack of * space bitbucket we can send it. */ if(skb) { if(aarp_send_ddp(rt->dev, skb, &ta, NULL) == -1) kfree_skb(skb); } return (0); } #if defined(CONFIG_IPDDP) || defined(CONFIG_IPDDP_MODULE) /* * Check if IP-over-DDP */ if(skb->data[12] == 22) { struct device *dev; /* This needs to be able to handle ipddp"N" devices */ if((dev = dev_get("ipddp0")) == NULL) return (-ENODEV); skb->protocol = htons(ETH_P_IP); skb_pull(skb, 13); skb->dev = dev; skb->h.raw = skb->data; ((struct net_device_stats *)dev->priv)->rx_packets++; ((struct net_device_stats *)dev->priv)->rx_bytes += skb->len+13; netif_rx(skb); /* Send the SKB up to a higher place. */ return (0); } #endif /* * Which socket - atalk_search_socket() looks for a *full match* * of the tuple. */ tosat.sat_addr.s_net = ddp->deh_dnet; tosat.sat_addr.s_node = ddp->deh_dnode; tosat.sat_port = ddp->deh_dport; sock = atalk_search_socket(&tosat, atif); if(sock == NULL) /* But not one of our sockets */ { kfree_skb(skb); return (0); } /* * Queue packet (standard) */ skb->sk = sock; if(sock_queue_rcv_skb(sock, skb) < 0) kfree_skb(skb); return (0); } /* * Receive a LocalTalk frame. We make some demands on the caller here. * Caller must provide enough headroom on the packet to pull the short * header and append a long one. */ static int ltalk_rcv(struct sk_buff *skb, struct device *dev, struct packet_type *pt) { struct ddpehdr *ddp; struct at_addr *ap; /* * Expand any short form frames. */ if(skb->mac.raw[2] == 1) { /* * Find our address. */ ap = atalk_find_dev_addr(dev); if(ap == NULL || skb->len < sizeof(struct ddpshdr)) { kfree_skb(skb); return (0); } /* * The push leaves us with a ddephdr not an shdr, and * handily the port bytes in the right place preset. */ skb_push(skb, sizeof(*ddp) - 4); ddp = (struct ddpehdr *)skb->data; /* * Now fill in the long header. */ /* * These two first. The mac overlays the new source/dest * network information so we MUST copy these before * we write the network numbers ! */ ddp->deh_dnode = skb->mac.raw[0]; /* From physical header */ ddp->deh_snode = skb->mac.raw[1]; /* From physical header */ ddp->deh_dnet = ap->s_net; /* Network number */ ddp->deh_snet = ap->s_net; ddp->deh_sum = 0; /* No checksum */ /* * Not sure about this bit... */ ddp->deh_len = skb->len; ddp->deh_hops = DDP_MAXHOPS; /* Non routable, so force a drop if we slip up later */ /* Mend the byte order */ *((__u16 *)ddp) = htons(*((__u16 *)ddp)); } skb->h.raw = skb->data; return (atalk_rcv(skb, dev, pt)); } static int atalk_sendmsg(struct socket *sock, struct msghdr *msg, int len, struct scm_cookie *scm) { struct sock *sk=sock->sk; struct sockaddr_at *usat=(struct sockaddr_at *)msg->msg_name; struct sockaddr_at local_satalk, gsat; struct sk_buff *skb; struct device *dev; struct ddpehdr *ddp; int size; struct atalk_route *rt; int loopback=0; int err; int flags = msg->msg_flags; if(flags & ~MSG_DONTWAIT) return (-EINVAL); if(len > DDP_MAXSZ) return (-EMSGSIZE); if(usat) { if(sk->zapped) { if(atalk_autobind(sk) < 0) return (-EBUSY); } if(msg->msg_namelen < sizeof(*usat)) return (-EINVAL); if(usat->sat_family != AF_APPLETALK) return (-EINVAL); /* netatalk doesn't implement this check */ if(usat->sat_addr.s_node == ATADDR_BCAST && !sk->broadcast) { printk(KERN_INFO "SO_BROADCAST: Fix your netatalk as it will break before 2.2\n"); #if 0 return (-EPERM); #endif } } else { if(sk->state != TCP_ESTABLISHED) return (-ENOTCONN); usat =& local_satalk; usat->sat_family = AF_APPLETALK; usat->sat_port = sk->protinfo.af_at.dest_port; usat->sat_addr.s_node = sk->protinfo.af_at.dest_node; usat->sat_addr.s_net = sk->protinfo.af_at.dest_net; } /* Build a packet */ SOCK_DEBUG(sk, "SK %p: Got address.\n",sk); /* For headers */ size = sizeof(struct ddpehdr) + len + ddp_dl->header_length; if(usat->sat_addr.s_net != 0 || usat->sat_addr.s_node == ATADDR_ANYNODE) { rt = atrtr_find(&usat->sat_addr); if(rt == NULL) return (-ENETUNREACH); dev = rt->dev; } else { struct at_addr at_hint; at_hint.s_node = 0; at_hint.s_net = sk->protinfo.af_at.src_net; rt = atrtr_find(&at_hint); if(rt == NULL) return (-ENETUNREACH); dev = rt->dev; } SOCK_DEBUG(sk, "SK %p: Size needed %d, device %s\n", sk, size, dev->name); size += dev->hard_header_len; skb = sock_alloc_send_skb(sk, size, 0, flags&MSG_DONTWAIT, &err); if(skb == NULL) return (err); skb->sk = sk; skb_reserve(skb, ddp_dl->header_length); skb_reserve(skb, dev->hard_header_len); skb->dev = dev; SOCK_DEBUG(sk, "SK %p: Begin build.\n", sk); ddp = (struct ddpehdr *)skb_put(skb,sizeof(struct ddpehdr)); ddp->deh_pad = 0; ddp->deh_hops = 0; ddp->deh_len = len + sizeof(*ddp); /* * Fix up the length field [Ok this is horrible but otherwise * I end up with unions of bit fields and messy bit field order * compiler/endian dependencies.. */ *((__u16 *)ddp) = ntohs(*((__u16 *)ddp)); ddp->deh_dnet = usat->sat_addr.s_net; ddp->deh_snet = sk->protinfo.af_at.src_net; ddp->deh_dnode = usat->sat_addr.s_node; ddp->deh_snode = sk->protinfo.af_at.src_node; ddp->deh_dport = usat->sat_port; ddp->deh_sport = sk->protinfo.af_at.src_port; SOCK_DEBUG(sk, "SK %p: Copy user data (%d bytes).\n", sk, len); err = memcpy_fromiovec(skb_put(skb,len), msg->msg_iov, len); if(err) { kfree_skb(skb); return (-EFAULT); } if(sk->no_check == 1) ddp->deh_sum = 0; else ddp->deh_sum = atalk_checksum(ddp, len + sizeof(*ddp)); if(call_out_firewall(PF_APPLETALK, skb->dev, ddp, NULL, &skb) != FW_ACCEPT) { kfree_skb(skb); return (-EPERM); } /* * Loopback broadcast packets to non gateway targets (ie routes * to group we are in) */ if(ddp->deh_dnode == ATADDR_BCAST) { if((!(rt->flags&RTF_GATEWAY)) && (!(dev->flags&IFF_LOOPBACK))) { struct sk_buff *skb2 = skb_copy(skb, GFP_KERNEL); if(skb2) { loopback = 1; SOCK_DEBUG(sk, "SK %p: send out(copy).\n", sk); if(aarp_send_ddp(dev, skb2, &usat->sat_addr, NULL) == -1) kfree_skb(skb2); /* else queued/sent above in the aarp queue */ } } } if((dev->flags & IFF_LOOPBACK) || loopback) { SOCK_DEBUG(sk, "SK %p: Loop back.\n", sk); /* loop back */ skb_orphan(skb); ddp_dl->datalink_header(ddp_dl, skb, dev->dev_addr); skb->mac.raw = skb->data; skb->h.raw = skb->data + ddp_dl->header_length + dev->hard_header_len; skb_pull(skb,dev->hard_header_len); skb_pull(skb,ddp_dl->header_length); atalk_rcv(skb, dev, NULL); } else { SOCK_DEBUG(sk, "SK %p: send out.\n", sk); if (rt->flags & RTF_GATEWAY) { gsat.sat_addr = rt->gateway; usat = &gsat; } if(aarp_send_ddp(dev, skb, &usat->sat_addr, NULL) == -1) kfree_skb(skb); /* else queued/sent above in the aarp queue */ } SOCK_DEBUG(sk, "SK %p: Done write (%d).\n", sk, len); return (len); } static int atalk_recvmsg(struct socket *sock, struct msghdr *msg, int size, int flags, struct scm_cookie *scm) { struct sock *sk=sock->sk; struct sockaddr_at *sat=(struct sockaddr_at *)msg->msg_name; struct ddpehdr *ddp = NULL; struct ddpebits ddphv; int copied = 0; struct sk_buff *skb; int err = 0; skb = skb_recv_datagram(sk,flags&~MSG_DONTWAIT,flags&MSG_DONTWAIT,&err); if(skb == NULL) return (err); ddp = (struct ddpehdr *)(skb->h.raw); *((__u16 *)&ddphv) = ntohs(*((__u16 *)ddp)); if(sk->type == SOCK_RAW) { copied = ddphv.deh_len; if(copied > size) { copied = size; msg->msg_flags |= MSG_TRUNC; } err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied); } else { copied = ddphv.deh_len - sizeof(*ddp); if(copied > size) { copied = size; msg->msg_flags |= MSG_TRUNC; } err = skb_copy_datagram_iovec(skb,sizeof(*ddp),msg->msg_iov,copied); } if(!err) { if(sat) { sat->sat_family = AF_APPLETALK; sat->sat_port = ddp->deh_sport; sat->sat_addr.s_node = ddp->deh_snode; sat->sat_addr.s_net = ddp->deh_snet; } msg->msg_namelen = sizeof(*sat); } skb_free_datagram(sk, skb); /* Free the datagram. */ return (err ? err : (copied)); } static int atalk_shutdown(struct socket *sk,int how) { return (-EOPNOTSUPP); } /* * AppleTalk ioctl calls. */ static int atalk_ioctl(struct socket *sock,unsigned int cmd, unsigned long arg) { long amount=0; struct sock *sk=sock->sk; switch(cmd) { /* * Protocol layer */ case TIOCOUTQ: amount = sk->sndbuf - atomic_read(&sk->wmem_alloc); if(amount < 0) amount = 0; break; 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 ddpehdr); break; } case SIOCGSTAMP: if(sk) { if(sk->stamp.tv_sec == 0) return -ENOENT; return (copy_to_user((void *)arg,&sk->stamp,sizeof(struct timeval)) ? -EFAULT : 0); } return (-EINVAL); /* * Routing */ case SIOCADDRT: case SIOCDELRT: if(!capable(CAP_NET_ADMIN)) return -EPERM; return (atrtr_ioctl(cmd,(void *)arg)); /* * Interface */ case SIOCGIFADDR: case SIOCSIFADDR: case SIOCGIFBRDADDR: case SIOCATALKDIFADDR: case SIOCDIFADDR: return (atif_ioctl(cmd,(void *)arg)); /* * Physical layer ioctl calls */ case SIOCSIFLINK: case SIOCGIFHWADDR: case SIOCSIFHWADDR: case SIOCGIFFLAGS: case SIOCSIFFLAGS: case SIOCGIFMTU: case SIOCGIFCONF: case SIOCADDMULTI: case SIOCDELMULTI: case SIOCGIFCOUNT: case SIOCGIFINDEX: case SIOCGIFNAME: return ((dev_ioctl(cmd,(void *) arg))); case SIOCSIFMETRIC: case SIOCSIFBRDADDR: case SIOCGIFNETMASK: case SIOCSIFNETMASK: case SIOCGIFMEM: case SIOCSIFMEM: case SIOCGIFDSTADDR: case SIOCSIFDSTADDR: return (-EINVAL); default: return (-EINVAL); } return (put_user(amount, (int *)arg)); } static struct net_proto_family atalk_family_ops= { PF_APPLETALK, atalk_create }; static struct proto_ops atalk_dgram_ops= { PF_APPLETALK, sock_no_dup, atalk_release, atalk_bind, atalk_connect, sock_no_socketpair, atalk_accept, atalk_getname, datagram_poll, atalk_ioctl, sock_no_listen, atalk_shutdown, sock_no_setsockopt, sock_no_getsockopt, sock_no_fcntl, atalk_sendmsg, atalk_recvmsg }; static struct notifier_block ddp_notifier= { ddp_device_event, NULL, 0 }; struct packet_type ltalk_packet_type= { 0, NULL, ltalk_rcv, NULL, NULL }; struct packet_type ppptalk_packet_type= { 0, NULL, atalk_rcv, NULL, NULL }; static char ddp_snap_id[] = {0x08, 0x00, 0x07, 0x80, 0x9B}; /* * Export symbols for use by drivers when AppleTalk is a module. */ EXPORT_SYMBOL(aarp_send_ddp); EXPORT_SYMBOL(atrtr_get_dev); EXPORT_SYMBOL(atalk_find_dev_addr); #ifdef CONFIG_PROC_FS static struct proc_dir_entry proc_appletalk= { PROC_NET_ATALK, 9, "appletalk", S_IFREG | S_IRUGO, 1, 0, 0, 0, &proc_net_inode_operations, atalk_get_info }; static struct proc_dir_entry proc_atalk_route= { PROC_NET_AT_ROUTE, 11,"atalk_route", S_IFREG | S_IRUGO, 1, 0, 0, 0, &proc_net_inode_operations, atalk_rt_get_info }; static struct proc_dir_entry proc_atalk_iface= { PROC_NET_ATIF, 11,"atalk_iface", S_IFREG | S_IRUGO, 1, 0, 0, 0, &proc_net_inode_operations, atalk_if_get_info }; #endif /* CONFIG_PROC_FS */ /* Called by proto.c on kernel start up */ __initfunc(void atalk_proto_init(struct net_proto *pro)) { (void) sock_register(&atalk_family_ops); if((ddp_dl = register_snap_client(ddp_snap_id, atalk_rcv)) == NULL) printk(KERN_CRIT "Unable to register DDP with SNAP.\n"); ltalk_packet_type.type = htons(ETH_P_LOCALTALK); dev_add_pack(<alk_packet_type); ppptalk_packet_type.type = htons(ETH_P_PPPTALK); dev_add_pack(&ppptalk_packet_type); register_netdevice_notifier(&ddp_notifier); aarp_proto_init(); #ifdef CONFIG_PROC_FS proc_net_register(&proc_appletalk); proc_net_register(&proc_atalk_route); proc_net_register(&proc_atalk_iface); #endif /* CONFIG_PROC_FS */ #ifdef CONFIG_SYSCTL atalk_register_sysctl(); #endif /* CONFIG_SYSCTL */ printk(KERN_INFO "AppleTalk 0.18 for Linux NET3.037\n"); } #ifdef MODULE int init_module(void) { atalk_proto_init(NULL); return (0); } /* * Note on MOD_{INC,DEC}_USE_COUNT: * * Use counts are incremented/decremented when * sockets are created/deleted. * * AppleTalk interfaces are not incremented untill atalkd is run * and are only decremented when they are downed. * * Ergo, before the AppleTalk module can be removed, all AppleTalk * sockets be closed from user space. */ void cleanup_module(void) { #ifdef CONFIG_SYSCTL atalk_unregister_sysctl(); #endif /* CONFIG_SYSCTL */ #ifdef CONFIG_PROC_FS proc_net_unregister(PROC_NET_ATALK); proc_net_unregister(PROC_NET_AT_ROUTE); proc_net_unregister(PROC_NET_ATIF); #endif /* CONFIG_PROC_FS */ aarp_cleanup_module(); /* General aarp clean-up. */ unregister_netdevice_notifier(&ddp_notifier); dev_remove_pack(<alk_packet_type); dev_remove_pack(&ppptalk_packet_type); unregister_snap_client(ddp_snap_id); sock_unregister(PF_APPLETALK); return; } #endif /* MODULE */ #endif /* CONFIG_ATALK || CONFIG_ATALK_MODULE */