/* * Copyright (c) 2000-2010 Apple Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. The rights granted to you under the License * may not be used to create, or enable the creation or redistribution of, * unlawful or unlicensed copies of an Apple operating system, or to * circumvent, violate, or enable the circumvention or violation of, any * terms of an Apple operating system software license agreement. * * Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ */ /* * Copyright (c) 1998 Apple Computer, Inc. */ /* at.c */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include int lap_online( at_ifaddr_t *, at_if_cfg_t *cfgp); extern int routerStart(at_kern_err_t *); extern void elap_offline(at_ifaddr_t *); extern at_ifaddr_t *find_ifID(char *); extern int xpatcnt; extern at_ifaddr_t at_interfaces[]; extern at_ifaddr_t *ifID_home; extern TAILQ_HEAD(name_registry, _nve_) name_registry; extern int nve_lock; struct etalk_addr etalk_multicast_addr = { {0x09, 0x00, 0x07, 0xff, 0xff, 0xff}}; struct etalk_addr ttalk_multicast_addr = { {0xC0, 0x00, 0x40, 0x00, 0x00, 0x00}}; /* called only in router mode */ static int set_zones(zone_usage_t *ifz) /* 1. adds zone to table 2. looks up each route entry from zone list 3. sets zone bit in each route entry returns 0 if successful errno if error occurred */ { int i; at_ifaddr_t *ifID; short zno; RT_entry *rte; if (ifz->zone_name.len <= 0 || ifz->zone_name.len > NBP_NVE_STR_SIZE) return(ENOSPC); zno = zt_add_zone((char *)ifz->zone_name.str, ifz->zone_name.len); if (zno == ZT_MAXEDOUT) { dPrintf(D_M_ELAP, D_L_ERROR, ("set_zones: error: table full\n")); return(ENOSPC); } if (ifz->zone_home) { ifID_home->ifZoneName = ifz->zone_name; ifID_home->ifDefZone = zno; } for (i=0; izone_iflist.at_if[i][0]) { if ((ifID = find_ifID(ifz->zone_iflist.at_if[i]))) { rte = rt_blookup(ifID->ifThisCableEnd); if (!rte) { dPrintf(D_M_ELAP, D_L_ERROR, ("set_zones: error: can't find route\n")); } else { zt_set_zmap(zno, rte->ZoneBitMap); /* if first zone for this I/F, make default */ if (!ifID->ifDefZone) ifID->ifDefZone = zno; } } } } return(0); } /* set_zones */ static int at_domifattach(struct ifnet *ifp, at_ifaddr_t *ifID) { int error; if ((error = proto_plumb(PF_APPLETALK, ifp))) { if (error != EEXIST) log(LOG_ERR, "%s: proto_plumb returned %d if=%s%d\n", __func__, error, ifp->if_name, ifp->if_unit); } else if (ifID) ifID->at_was_attached = 1; return (error); } /* * Generic internet control operations (ioctl's). * ifp is 0 if not an interface-specific ioctl. */ int at_control(so, cmd, data, ifp) struct socket *so; u_long cmd; caddr_t data; struct ifnet *ifp; { struct ifreq *ifr = (struct ifreq *)data; int pat_id = 0, error = 0; at_ifaddr_t *ifID = 0; struct ifaddr *ifa; struct sockaddr_dl *sdl; if ((cmd & 0xffff) == 0xff99) { u_long fixed_command; /* *** this is a temporary hack to get at_send_to_dev() to work with BSD-style sockets instead of the special purpose system calls, ATsocket() and ATioctl(). *** */ fixed_command = _IOW(0, 0xff99, user_addr_t); if ((error = at_ioctl((struct atpcb *)so->so_pcb, fixed_command, data, 0))) { if (((struct atpcb *)so->so_pcb)->proto != ATPROTO_LAP) { ((struct atpcb *)so->so_pcb)->proto = ATPROTO_LAP; error = at_ioctl((struct atpcb *)so->so_pcb, fixed_command, data , 0); } } return(error); /* *** processing should be return(EINVAL); *** */ } /* * Find address for this interface, if it exists. */ if (ifp) for (pat_id = 0; pat_id < xpatcnt; pat_id++) if (at_interfaces[pat_id].aa_ifp == ifp) { ifID = &at_interfaces[pat_id]; break; } switch (cmd) { case AIOCGETSTATE: { at_state_t *global_state = (at_state_t *)data; *global_state = at_state; return(0); break; } case AIOCGETIFCFG: { at_if_cfg_t *cfgp = (at_if_cfg_t *)data; ifID = 0; if ((at_state.flags & AT_ST_STARTED) && ifID_home) { if (strlen(cfgp->ifr_name)) { TAILQ_FOREACH(ifID, &at_ifQueueHd, aa_link) { if (!strncmp(ifID->ifName, cfgp->ifr_name, strlen(ifID->ifName))) break; } } else { ifID = ifID_home; strlcpy(cfgp->ifr_name, ifID->ifName, sizeof(cfgp->ifr_name)); } if (ifID && ifID->ifState != LAP_OFFLINE) { cfgp->flags = ifID->ifFlags; /* put the IF state into the low order bits of flags */ cfgp->flags |= (ifID->ifState & LAP_STATE_MASK); cfgp->node = ifID->ifThisNode; cfgp->router = ifID->ifARouter; cfgp->netStart = ifID->ifThisCableStart; cfgp->netEnd = ifID->ifThisCableEnd; cfgp->zonename = ifID->ifZoneName; return(0); } else return(EINVAL); } else return(ENOTREADY); break; } case AIOCSETDEFZONE: { at_def_zone_t *defzonep = (at_def_zone_t *)data; /* check for root access */ if ((error = suser(kauth_cred_get(), 0))) return(EACCES); ifID = 0; if ((at_state.flags & AT_ST_STARTED) && ifID_home) { if (strlen(defzonep->ifr_name)) { TAILQ_FOREACH(ifID, &at_ifQueueHd, aa_link) { if (!strncmp(ifID->ifName, defzonep->ifr_name, strlen(ifID->ifName))) break; } } else { ifID = ifID_home; strlcpy(defzonep->ifr_name, ifID->ifName, sizeof(defzonep->ifr_name)); } /* In routing mode the default zone is only set for the default interface. */ if (ROUTING_MODE && (ifID != ifID_home)) return(EINVAL); if (ifID && ifID->ifState != LAP_OFFLINE) { if (zonename_equal(&ifID->ifZoneName, &defzonep->zonename)) return(0); else { /* check the zone name */ if (MULTIPORT_MODE) { short zno; at_ifnames_t ifs_in_zone; if (!(zno = zt_find_zname(&defzonep->zonename))) return(EINVAL); getIfUsage(zno-1, &ifs_in_zone); if (!ifs_in_zone.at_if[ifID->ifPort]) return(EINVAL); ifID->ifDefZone = zno+1; } else { int i; at_nvestr_t *zone; for (i = 0, zone = getSPLocalZone(i); zone; i++, zone = getSPLocalZone(i)) { if (zonename_equal(zone, &defzonep->zonename)) break; } if (!zone) return(EINVAL); } ifID->ifZoneName = defzonep->zonename; (void)regDefaultZone(ifID); /* AppleTalk zone was changed. Send event with zone info. */ atalk_post_msg(ifID->aa_ifp, KEV_ATALK_ZONEUPDATED, 0, &(ifID->ifZoneName)); return(0); } } else return(EINVAL); } else return(ENOTREADY); break; } case AIOCREGLOCALZN: { at_nvestr_t *zone = (at_nvestr_t *)data; if (!(at_state.flags & AT_ST_STARTED) || !ifID_home) return(ENOTREADY); if (MULTIPORT_MODE) return(EINVAL); return(setLocalZones(zone, zone->len)); break; } case AIOCSETZNUSAGE: if (!(at_state.flags & AT_ST_STARTED) || !ifID_home) return(ENOTREADY); if (!ROUTING_MODE) return(EINVAL); return(set_zones((zone_usage_t *)data)); break; case AIOCGETZNUSAGE: if (!(at_state.flags & AT_ST_STARTED) || !ifID_home) return(ENOTREADY); if (!MULTIPORT_MODE) return(EINVAL); if (getRTRLocalZone((zone_usage_t *)data)) return(0); else return(ENOENT); break; case AIOCNBPREG: { at_nbp_reg_t *nbpP = (at_nbp_reg_t *)data; nve_entry_t nve; int error2; if (!(at_state.flags & AT_ST_STARTED) || !ifID_home) return(ENOTREADY); /* multihoming mode */ if (MULTIHOME_MODE) { return(nbp_mh_reg(nbpP)); } /* single port mode or router mode */ if (nbp_fillin_nve(&nbpP->name, &nve) != 0) { /* bad tuple... */ return(EINVAL); } /* In routing mode when the zone is specified, we need to find an interface on which the specified zone is seeded, so that the zone multicast will be plausible. */ if (ROUTING_MODE && !(DEFAULT_ZONE(&nve.zone))) { /* find first segment (interface) which is seeded for this zone */ int finished = FALSE; int zno; at_ifnames_t ifs_in_zone; if (!(zno = zt_find_zname(&nve.zone))) { return(EINVAL); } getIfUsage(zno-1, &ifs_in_zone); TAILQ_FOREACH(ifID, &at_ifQueueHd, aa_link) { if (!ifs_in_zone.at_if[ifID->ifPort]) /* zone doesn't match */ continue; else { finished = TRUE; break; } } if (!finished) return(EINVAL); } else ifID = ifID_home; nve.address.net = ifID->ifThisNode.s_net; nve.address.node = ifID->ifThisNode.s_node; nve.address.socket = nbpP->addr.socket; nve.ddptype = nbpP->ddptype; if (nbp_find_nve(&nve)) return(EADDRNOTAVAIL); /* Normal case; no tuple found for this name, so insert * this tuple in the registry and return ok response. */ if ((error2 = nbp_new_nve_entry(&nve, ifID)) == 0) { nbpP->addr.net = ifID->ifThisNode.s_net; nbpP->addr.node = ifID->ifThisNode.s_node; nbpP->unique_nbp_id = nve.unique_nbp_id; } return(error2); break; } case AIOCNBPREMOVE: { at_nbp_reg_t *nbpP = (at_nbp_reg_t *)data; nve_entry_t *nve_entry, nve; if (!(at_state.flags & AT_ST_STARTED)) return(ENOTREADY); /* delete by id */ if (nbpP->unique_nbp_id) { TAILQ_FOREACH(nve_entry, &name_registry, nve_link) { if (nve_entry->unique_nbp_id == nbpP->unique_nbp_id) { /* Found a match! */ nbp_delete_entry(nve_entry); return(0); } } return(EADDRNOTAVAIL); } /* delete by entity */ if (nbp_fillin_nve(&nbpP->name, &nve) != 0) { /* bad tuple... */ return(EINVAL); } if (MULTIHOME_MODE && DEFAULT_ZONE(&nbpP->name.zone)) { /* if mhome & *, remove nve from all default zones */ int found = FALSE; /* if any found & deleted */ TAILQ_FOREACH(ifID, &at_ifQueueHd, aa_link) { nve.zone = ifID->ifZoneName; nve.zone_hash = nbp_strhash(&nve.zone); if ((nve_entry = nbp_find_nve(&nve)) == NULL) continue; nbp_delete_entry(nve_entry); found = TRUE; } if (found) return(0); else return(EADDRNOTAVAIL); } if ((nve_entry = nbp_find_nve(&nve)) == NULL) /* Can't find the tuple we're looking for, send error*/ return(EADDRNOTAVAIL); /* Normal case; tuple found for this name, so delete * the entry from the registry and return ok response. */ nbp_delete_entry(nve_entry); return(0); break; } case AIOCSETROUTER: { at_router_params_t *rt = (at_router_params_t *)data; /* check for root access */ if ((error = suser(kauth_cred_get(), 0))) return(EACCES); /* when in routing/multihome mode the AIOCSETROUTER IOCTL is done first */ if (at_state.flags & AT_ST_STARTED) return(EALREADY); /* Setup the routing & zip table size for the router */ if (rt->rtmp_table_sz >= RT_MIN && rt->rtmp_table_sz <= RT_MAX) RT_maxentry = rt->rtmp_table_sz; else RT_maxentry = RT_DEFAULT; if (rt->zone_table_sz >= ZT_MIN && rt->zone_table_sz <= ZT_MAX) ZT_maxentry = rt->zone_table_sz; else ZT_maxentry = ZT_DEFAULT; if (rt_table_init() == ENOBUFS) return(ENOBUFS); if (rt->router_mix) RouterMix = (int)rt->router_mix; else RouterMix = RT_MIX_DEFAULT; add_ddp_handler(RTMP_SOCKET, rtmp_router_input); if (rt->multihome) at_state.flags |= AT_ST_MULTIHOME; else at_state.flags |= AT_ST_ROUTER; break; } case AIOCSTARTROUTER: { at_kern_err_t *keP = (at_kern_err_t *)data; /* check for root access */ if (suser(kauth_cred_get(), 0)) return(EACCES); if (!(at_state.flags & AT_ST_STARTED)) return(ENOTREADY); bzero(keP, sizeof(at_kern_err_t)); error = routerStart(keP); break; } case AIOCGETROUTER: { at_router_params_t *rt = (at_router_params_t *)data; if (!(at_state.flags & AT_ST_STARTED)) return(ENOTREADY); rt->multihome = (MULTIHOME_MODE)? 1: 0; rt->rtmp_table_sz = RT_maxentry; rt->zone_table_sz = ZT_maxentry; rt->router_mix = RouterMix; break; } case AIOCSTOPATALK: { int *count_only = (int *)data, ret; /* check for root access */ if ((error = suser(kauth_cred_get(), 0))) return(EACCES); ret = ddp_shutdown(*count_only); if (*count_only != 0) { *count_only = ret; return(0); } else { if (ret == 0) { /* AppleTalk was successfully shut down. Send event. */ atalk_post_msg(0, KEV_ATALK_DISABLED, 0, 0); return 0; } else return EBUSY; } break; } case SIOCSIFADDR: /* check for root access */ if ((error = suser(kauth_cred_get(), 0))) error = EACCES; else if (ifID) error = EEXIST; else { if (xpatcnt == 0) { at_state.flags |= AT_ST_STARTING; ddp_brt_init(); } /* *** find an empty entry *** */ ifID = &at_interfaces[xpatcnt]; bzero((caddr_t)ifID, sizeof(at_ifaddr_t)); strlcpy(ifID->ifName, ifr->ifr_name, sizeof(ifID->ifName)); ifID->aa_ifp = ifp; ifa = &ifID->aa_ifa; error = at_domifattach(ifp, ifID); if (error == EEXIST) { ifID->at_was_attached = 1; error = 0; } if (error != 0) { break; } /* XXX ethernet-specific */ ifID->cable_multicast_addr = etalk_multicast_addr; xpatcnt++; ifnet_lock_exclusive(ifp); /* * Holding ifnet lock here prevents the link address * from changing contents, so no need to hold the ifa * lock. The link address is always present; it's * never freed. */ sdl = (struct sockaddr_dl *)ifp->if_lladdr->ifa_addr; bcopy(LLADDR(sdl), ifID->xaddr, sizeof(ifID->xaddr)); #ifdef APPLETALK_DEBUG kprintf("SIOCSIFADDR: local enet address is " "%x.%x.%x.%x.%x.%x\n", ifID->xaddr[0], ifID->xaddr[1], ifID->xaddr[2], ifID->xaddr[3], ifID->xaddr[4], ifID->xaddr[5]); #endif /* attach the AppleTalk address to the ifnet structure */ ifa = &ifID->aa_ifa; ifa_lock_init(ifa); VERIFY(!(ifa->ifa_debug & IFD_ALLOC)); ifa->ifa_addr = (struct sockaddr *)&ifID->ifNodeAddress; ifID->ifNodeAddress.sat_len = sizeof(struct sockaddr_at); ifID->ifNodeAddress.sat_family = AF_APPLETALK; /* the address itself will be filled in when ifThisNode is set */ IFA_LOCK(ifa); if_attach_ifa(ifp, ifa); /* add a reference for at_interfaces[] */ IFA_ADDREF_LOCKED(ifa); IFA_UNLOCK(ifa); ifnet_lock_done(ifp); } break; /* complete the initialization started in SIOCSIFADDR */ case AIOCSIFADDR: { at_if_cfg_t *cfgp = (at_if_cfg_t *)data; if (!(at_state.flags & AT_ST_STARTING)) return(ENOTREADY); if (!(ifID = find_ifID(cfgp->ifr_name))) return(EINVAL); return(lap_online(ifID, cfgp)); break; } #ifdef NOT_YET /* *** this can't be added until AT can handle dynamic addition and deletion of interfaces *** */ case SIOCDIFADDR: /* check for root access */ if (error = suser(kauth_cred_get(), 0)) error = EACCES; else if (!ifID) error = EINVAL; else elap_offline(ifID); break; #endif case SIOCSETOT: { struct atpcb *at_pcb, *clonedat_pcb; int cloned_fd = *(int *)data; at_pcb = sotoatpcb(so); /* let's make sure it's either -1 or a valid file descriptor */ if (cloned_fd != -1) { struct socket *cloned_so; error = file_socket(cloned_fd, &cloned_so); if (error) break; clonedat_pcb = sotoatpcb(cloned_so); } else { clonedat_pcb = NULL; } if (clonedat_pcb == NULL) { at_pcb->ddp_flags |= DDPFLG_STRIPHDR; } else { at_pcb->ddp_flags = clonedat_pcb->ddp_flags; } file_drop(cloned_fd); break; } case SIOCPROTOATTACH: /* check for root access */ if (suser(kauth_cred_get(), 0) != 0) { error = EACCES; break; } error = at_domifattach(ifp, ifID); break; case SIOCPROTODETACH: /* check for root access */ if (suser(kauth_cred_get(), 0) != 0) { error = EACCES; break; } if (ifID != NULL) { error = EBUSY; break; } error = proto_unplumb(PF_APPLETALK, ifp); break; default: if (ifp == 0 || ifp->if_ioctl == 0) return (EOPNOTSUPP); return ifnet_ioctl(ifp, 0, cmd, data); } return(error); } /* From dlil_post_msg() */ void atalk_post_msg(struct ifnet *ifp, u_long event_code, struct at_addr *address, at_nvestr_t *zone) { struct kev_atalk_data at_event_data; struct kev_msg ev_msg; bzero(&ev_msg, sizeof(struct kev_msg)); ev_msg.vendor_code = KEV_VENDOR_APPLE; ev_msg.kev_class = KEV_NETWORK_CLASS; ev_msg.kev_subclass = KEV_ATALK_SUBCLASS; ev_msg.event_code = event_code; bzero(&at_event_data, sizeof(struct kev_atalk_data)); if (ifp != 0) { strlcpy(&at_event_data.link_data.if_name[0], ifp->if_name, IFNAMSIZ); at_event_data.link_data.if_family = ifp->if_family; at_event_data.link_data.if_unit = (unsigned long) ifp->if_unit; } if (address != 0) { at_event_data.node_data.address = *address; } else if (zone != 0) { at_event_data.node_data.zone = *zone; } ev_msg.dv[0].data_length = sizeof(struct kev_atalk_data); ev_msg.dv[0].data_ptr = &at_event_data; ev_msg.dv[1].data_length = 0; kev_post_msg(&ev_msg); } /* * This is untested; the code is here only for completeness. */ void at_purgeaddrs(struct ifnet *ifp) { at_ifaddr_t *ifID = NULL; int pat_id; /* Find address for this interface, if it exists */ for (pat_id = 0; pat_id < xpatcnt; pat_id++) { if (at_interfaces[pat_id].aa_ifp == ifp) { ifID = &at_interfaces[pat_id]; elap_offline(ifID); } } }