1/*
2 * Copyright (c) 2000-2010 Apple Inc. All rights reserved.
3 *
4 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. The rights granted to you under the License
10 * may not be used to create, or enable the creation or redistribution of,
11 * unlawful or unlicensed copies of an Apple operating system, or to
12 * circumvent, violate, or enable the circumvention or violation of, any
13 * terms of an Apple operating system software license agreement.
14 *
15 * Please obtain a copy of the License at
16 * http://www.opensource.apple.com/apsl/ and read it before using this file.
17 *
18 * The Original Code and all software distributed under the License are
19 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23 * Please see the License for the specific language governing rights and
24 * limitations under the License.
25 *
26 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27 */
28/*
29 *	Copyright (c) 1998 Apple Computer, Inc.
30 */
31
32/*	at.c
33 */
34
35#include <sys/param.h>
36#include <sys/systm.h>
37#include <sys/ioctl.h>
38#include <sys/errno.h>
39#include <sys/malloc.h>
40#include <sys/socket.h>
41#include <sys/socketvar.h>
42#include <sys/file.h>
43#include <sys/kauth.h>
44
45#include <net/if.h>
46#include <net/if_dl.h>
47#include <net/if_types.h>
48#include <net/dlil.h>
49
50#include <netat/sysglue.h>
51#include <netat/appletalk.h>
52#include <netat/at_pcb.h>
53#include <netat/at_var.h>
54#include <netat/ddp.h>
55#include <netat/nbp.h>
56#include <netat/routing_tables.h>
57#include <netat/debug.h>
58
59#include <sys/kern_event.h>
60#include <net/kpi_protocol.h>
61
62int lap_online( at_ifaddr_t *, at_if_cfg_t *cfgp);
63
64extern int routerStart(at_kern_err_t *);
65extern void elap_offline(at_ifaddr_t *);
66extern at_ifaddr_t *find_ifID(char *);
67
68extern int xpatcnt;
69extern at_ifaddr_t at_interfaces[];
70extern at_ifaddr_t *ifID_home;
71extern TAILQ_HEAD(name_registry, _nve_) name_registry;
72extern int nve_lock;
73
74struct  etalk_addr      etalk_multicast_addr = {
75  {0x09, 0x00, 0x07, 0xff, 0xff, 0xff}};
76struct  etalk_addr      ttalk_multicast_addr = {
77  {0xC0, 0x00, 0x40, 0x00, 0x00, 0x00}};
78
79/* called only in router mode */
80static int set_zones(zone_usage_t *ifz)
81
82/* 1. adds zone to table
83   2. looks up each route entry from zone list
84   3. sets zone bit in each route entry
85
86   returns  0 if successful
87	    errno if error occurred
88*/
89{
90	int i;
91	at_ifaddr_t *ifID;
92	short zno;
93	RT_entry *rte;
94
95	if (ifz->zone_name.len <= 0 || ifz->zone_name.len > NBP_NVE_STR_SIZE)
96		return(ENOSPC);
97
98	zno = zt_add_zone((char *)ifz->zone_name.str, ifz->zone_name.len);
99
100	if (zno == ZT_MAXEDOUT) {
101		dPrintf(D_M_ELAP, D_L_ERROR, ("set_zones: error: table full\n"));
102		return(ENOSPC);
103	}
104	if (ifz->zone_home) {
105		ifID_home->ifZoneName = ifz->zone_name;
106		ifID_home->ifDefZone = zno;
107	}
108
109	for (i=0; i<IF_TOTAL_MAX; i++)  {
110		if (ifz->zone_iflist.at_if[i][0]) {
111			if ((ifID = find_ifID(ifz->zone_iflist.at_if[i]))) {
112				rte = rt_blookup(ifID->ifThisCableEnd);
113				if (!rte) {
114					dPrintf(D_M_ELAP, D_L_ERROR,
115						("set_zones: error: can't find route\n"));
116				} else {
117					zt_set_zmap(zno, rte->ZoneBitMap);
118
119					/* if first zone for this I/F,
120					   make default */
121					if (!ifID->ifDefZone)
122						ifID->ifDefZone = zno;
123				}
124			}
125		}
126	}
127
128	return(0);
129} /* set_zones */
130
131static int
132at_domifattach(struct ifnet *ifp, at_ifaddr_t *ifID)
133{
134	int error;
135
136	if ((error = proto_plumb(PF_APPLETALK, ifp))) {
137		if (error != EEXIST)
138			log(LOG_ERR, "%s: proto_plumb returned %d if=%s%d\n",
139			    __func__, error, ifp->if_name, ifp->if_unit);
140	} else if (ifID)
141		ifID->at_was_attached = 1;
142
143	return (error);
144}
145
146/*
147  * Generic internet control operations (ioctl's).
148  * ifp is 0 if not an interface-specific ioctl.
149  */
150
151int
152at_control(so, cmd, data, ifp)
153     struct socket *so;
154     u_long cmd;
155     caddr_t data;
156     struct ifnet *ifp;
157{
158	struct ifreq *ifr = (struct ifreq *)data;
159	int pat_id = 0, error = 0;
160	at_ifaddr_t *ifID = 0;
161	struct ifaddr *ifa;
162	struct sockaddr_dl *sdl;
163
164    if ((cmd & 0xffff) == 0xff99) {
165		u_long 	fixed_command;
166		/* *** this is a temporary hack to get at_send_to_dev() to
167		   work with BSD-style sockets instead of the special purpose
168		   system calls, ATsocket() and ATioctl().
169		   *** */
170		fixed_command = _IOW(0, 0xff99, user_addr_t);
171		if ((error = at_ioctl((struct atpcb *)so->so_pcb, fixed_command, data, 0))) {
172		  if (((struct atpcb *)so->so_pcb)->proto != ATPROTO_LAP) {
173		    ((struct atpcb *)so->so_pcb)->proto = ATPROTO_LAP;
174		    error = at_ioctl((struct atpcb *)so->so_pcb, fixed_command, data , 0);
175		  }
176		}
177		return(error);
178
179		/* *** processing should be
180		   return(EINVAL);
181		   *** */
182	}
183        /*
184	 * Find address for this interface, if it exists.
185	 */
186	if (ifp)
187		for (pat_id = 0; pat_id < xpatcnt; pat_id++)
188		  if (at_interfaces[pat_id].aa_ifp == ifp) {
189			ifID = &at_interfaces[pat_id];
190			break;
191		  }
192
193	switch (cmd) {
194
195	case AIOCGETSTATE:
196	  {
197	  	at_state_t *global_state = (at_state_t *)data;
198
199		*global_state = at_state;
200		return(0);
201		break;
202	  }
203
204	case AIOCGETIFCFG:
205	  {
206	  	at_if_cfg_t *cfgp = (at_if_cfg_t *)data;
207
208		ifID = 0;
209		if ((at_state.flags & AT_ST_STARTED) &&
210		    ifID_home) {
211			if (strlen(cfgp->ifr_name)) {
212				TAILQ_FOREACH(ifID, &at_ifQueueHd, aa_link) {
213					if (!strncmp(ifID->ifName, cfgp->ifr_name,
214						     strlen(ifID->ifName)))
215						break;
216				}
217			} else {
218				ifID = ifID_home;
219				strlcpy(cfgp->ifr_name, ifID->ifName,
220					sizeof(cfgp->ifr_name));
221			}
222			if  (ifID && ifID->ifState != LAP_OFFLINE) {
223				cfgp->flags = ifID->ifFlags;
224				/* put the IF state into the low order
225				   bits of flags */
226				cfgp->flags |= (ifID->ifState & LAP_STATE_MASK);
227				cfgp->node = ifID->ifThisNode;
228				cfgp->router = ifID->ifARouter;
229				cfgp->netStart = ifID->ifThisCableStart;
230				cfgp->netEnd = ifID->ifThisCableEnd;
231				cfgp->zonename = ifID->ifZoneName;
232				return(0);
233			} else
234				return(EINVAL);
235		} else
236			return(ENOTREADY);
237		break;
238	  }
239
240	case AIOCSETDEFZONE:
241	  {
242	  	at_def_zone_t *defzonep = (at_def_zone_t *)data;
243
244		/* check for root access */
245		if ((error = suser(kauth_cred_get(), 0)))
246			return(EACCES);
247
248		ifID = 0;
249		if ((at_state.flags & AT_ST_STARTED) && ifID_home) {
250			if (strlen(defzonep->ifr_name)) {
251			    TAILQ_FOREACH(ifID, &at_ifQueueHd, aa_link) {
252				if (!strncmp(ifID->ifName, defzonep->ifr_name,
253					     strlen(ifID->ifName)))
254				    break;
255			    }
256			} else {
257				ifID = ifID_home;
258				strlcpy(defzonep->ifr_name, ifID->ifName,
259					sizeof(defzonep->ifr_name));
260			}
261
262			/* In routing mode the default zone is only set for the
263			   default interface. */
264			if (ROUTING_MODE && (ifID != ifID_home))
265				return(EINVAL);
266
267			if  (ifID && ifID->ifState != LAP_OFFLINE) {
268				if (zonename_equal(&ifID->ifZoneName,
269						   &defzonep->zonename))
270					return(0);
271				else {
272					/* check the zone name */
273					if (MULTIPORT_MODE) {
274					  short zno;
275					  at_ifnames_t ifs_in_zone;
276
277					  if (!(zno = zt_find_zname(&defzonep->zonename)))
278					    return(EINVAL);
279
280					  getIfUsage(zno-1, &ifs_in_zone);
281					  if (!ifs_in_zone.at_if[ifID->ifPort])
282					    return(EINVAL);
283					  ifID->ifDefZone = zno+1;
284					} else {
285					  int i;
286					  at_nvestr_t *zone;
287
288					  for (i = 0, zone = getSPLocalZone(i);
289					       zone;
290					       i++, zone = getSPLocalZone(i)) {
291					    if (zonename_equal(zone,
292							       &defzonep->zonename))
293					      break;
294					  }
295					  if (!zone)
296					    return(EINVAL);
297					}
298					ifID->ifZoneName = defzonep->zonename;
299					(void)regDefaultZone(ifID);
300
301					/* AppleTalk zone was changed. Send event with zone info. */
302					atalk_post_msg(ifID->aa_ifp, KEV_ATALK_ZONEUPDATED, 0, &(ifID->ifZoneName));
303
304					return(0);
305				}
306			} else
307				return(EINVAL);
308		} else
309			return(ENOTREADY);
310		break;
311	  }
312
313	case AIOCREGLOCALZN:
314	  {
315		at_nvestr_t *zone = (at_nvestr_t *)data;
316
317		if (!(at_state.flags & AT_ST_STARTED) || !ifID_home)
318			return(ENOTREADY);
319
320		if (MULTIPORT_MODE)
321			return(EINVAL);
322
323		return(setLocalZones(zone, zone->len));
324
325		break;
326	  }
327	case AIOCSETZNUSAGE:
328		if (!(at_state.flags & AT_ST_STARTED) || !ifID_home)
329			return(ENOTREADY);
330
331		if (!ROUTING_MODE)
332			return(EINVAL);
333
334		return(set_zones((zone_usage_t *)data));
335
336		break;
337
338	case AIOCGETZNUSAGE:
339		if (!(at_state.flags & AT_ST_STARTED) || !ifID_home)
340			return(ENOTREADY);
341
342		if (!MULTIPORT_MODE)
343			return(EINVAL);
344
345		if (getRTRLocalZone((zone_usage_t *)data))
346			return(0);
347		else
348			return(ENOENT);
349		break;
350
351	case AIOCNBPREG:
352	  {
353	  	at_nbp_reg_t *nbpP = (at_nbp_reg_t *)data;
354		nve_entry_t nve;
355		int error2;
356
357		if (!(at_state.flags & AT_ST_STARTED) || !ifID_home)
358			return(ENOTREADY);
359
360		/* multihoming mode */
361		if (MULTIHOME_MODE) {
362			return(nbp_mh_reg(nbpP));
363		}
364
365		/* single port mode or router mode */
366		if (nbp_fillin_nve(&nbpP->name, &nve) != 0) {
367			/* bad tuple... */
368			return(EINVAL);
369		}
370
371		/* In routing mode when the zone is specified, we need to
372		   find an interface on which the specified zone is seeded, so
373		   that the zone multicast will be plausible. */
374		if (ROUTING_MODE && !(DEFAULT_ZONE(&nve.zone))) {
375		        /* find first segment (interface) which is seeded for
376			   this zone */
377			int finished = FALSE;
378			int zno;
379			at_ifnames_t ifs_in_zone;
380			if (!(zno = zt_find_zname(&nve.zone))) {
381				return(EINVAL);
382			}
383			getIfUsage(zno-1, &ifs_in_zone);
384
385			TAILQ_FOREACH(ifID, &at_ifQueueHd, aa_link) {
386				if (!ifs_in_zone.at_if[ifID->ifPort])
387						/* zone doesn't match */
388					continue;
389				else {
390					finished = TRUE;
391					break;
392				}
393			}
394			if (!finished)
395				return(EINVAL);
396		} else
397			ifID = ifID_home;
398
399		nve.address.net = ifID->ifThisNode.s_net;
400		nve.address.node = ifID->ifThisNode.s_node;
401		nve.address.socket = nbpP->addr.socket;
402		nve.ddptype = nbpP->ddptype;
403
404		if (nbp_find_nve(&nve))
405			return(EADDRNOTAVAIL);
406
407		/* Normal case; no tuple found for this name, so insert
408		 * this tuple in the registry and return ok response.
409		 */
410		if ((error2 = nbp_new_nve_entry(&nve, ifID)) == 0) {
411			nbpP->addr.net = ifID->ifThisNode.s_net;
412			nbpP->addr.node = ifID->ifThisNode.s_node;
413			nbpP->unique_nbp_id = nve.unique_nbp_id;
414		}
415
416		return(error2);
417		break;
418	  }
419
420	case AIOCNBPREMOVE:
421	  {
422	  	at_nbp_reg_t *nbpP = (at_nbp_reg_t *)data;
423		nve_entry_t    *nve_entry, nve;
424
425		if (!(at_state.flags & AT_ST_STARTED))
426			return(ENOTREADY);
427
428		/* delete by id */
429		if (nbpP->unique_nbp_id) {
430			TAILQ_FOREACH(nve_entry, &name_registry, nve_link) {
431				if (nve_entry->unique_nbp_id == nbpP->unique_nbp_id) {
432					/* Found a match! */
433					nbp_delete_entry(nve_entry);
434					return(0);
435				}
436			}
437			return(EADDRNOTAVAIL);
438		}
439
440		/* delete by entity */
441		if (nbp_fillin_nve(&nbpP->name, &nve) != 0) {
442			/* bad tuple... */
443			return(EINVAL);
444		}
445
446		if (MULTIHOME_MODE && DEFAULT_ZONE(&nbpP->name.zone)) {
447			/* if mhome & *, remove nve from all default zones */
448			int found = FALSE;	/* if any found & deleted */
449
450			TAILQ_FOREACH(ifID, &at_ifQueueHd, aa_link) {
451				nve.zone = ifID->ifZoneName;
452				nve.zone_hash = nbp_strhash(&nve.zone);
453				if ((nve_entry = nbp_find_nve(&nve)) == NULL)
454					continue;
455
456				nbp_delete_entry(nve_entry);
457				found = TRUE;
458			}
459			if (found)
460				return(0);
461			else
462				return(EADDRNOTAVAIL);
463		}
464
465		if ((nve_entry = nbp_find_nve(&nve)) == NULL)
466			/* Can't find the tuple we're looking for, send error*/
467			return(EADDRNOTAVAIL);
468
469		/* Normal case; tuple found for this name, so delete
470		 * the entry from the registry and return ok response.
471		 */
472		nbp_delete_entry(nve_entry);
473		return(0);
474
475		break;
476	  }
477
478	case AIOCSETROUTER:
479	  {
480	  	at_router_params_t *rt = (at_router_params_t *)data;
481
482		/* check for root access */
483		if ((error = suser(kauth_cred_get(), 0)))
484			return(EACCES);
485
486		/* when in routing/multihome mode the AIOCSETROUTER IOCTL
487		   is done first */
488		if (at_state.flags & AT_ST_STARTED)
489	    		return(EALREADY);
490
491		/* Setup the routing & zip table size for the router */
492		if (rt->rtmp_table_sz >= RT_MIN && rt->rtmp_table_sz <= RT_MAX)
493                	RT_maxentry = rt->rtmp_table_sz;
494		else
495	                RT_maxentry = RT_DEFAULT;
496
497		if (rt->zone_table_sz >= ZT_MIN && rt->zone_table_sz <= ZT_MAX)
498			ZT_maxentry = rt->zone_table_sz;
499		else
500                	ZT_maxentry = ZT_DEFAULT;
501
502		if (rt_table_init() == ENOBUFS)
503			return(ENOBUFS);
504
505		if (rt->router_mix)
506			RouterMix = (int)rt->router_mix;
507		else
508			RouterMix = RT_MIX_DEFAULT;
509
510		add_ddp_handler(RTMP_SOCKET, rtmp_router_input);
511
512		if (rt->multihome)
513                	at_state.flags |= AT_ST_MULTIHOME;
514		else
515			at_state.flags |= AT_ST_ROUTER;
516		break;
517	  }
518	case AIOCSTARTROUTER:
519	  {
520	  	at_kern_err_t *keP = (at_kern_err_t *)data;
521
522		/* check for root access */
523		if (suser(kauth_cred_get(), 0))
524			return(EACCES);
525
526		if (!(at_state.flags & AT_ST_STARTED))
527			return(ENOTREADY);
528
529		bzero(keP, sizeof(at_kern_err_t));
530		error = routerStart(keP);
531
532		break;
533	  }
534	case AIOCGETROUTER:
535	  {
536	  	at_router_params_t *rt = (at_router_params_t *)data;
537
538		if (!(at_state.flags & AT_ST_STARTED))
539			return(ENOTREADY);
540
541		rt->multihome = (MULTIHOME_MODE)? 1: 0;
542		rt->rtmp_table_sz = RT_maxentry;
543		rt->zone_table_sz = ZT_maxentry;
544		rt->router_mix = RouterMix;
545
546		break;
547	  }
548	case AIOCSTOPATALK:
549	{
550		int *count_only = (int *)data,
551		    ret;
552
553		/* check for root access */
554		if ((error = suser(kauth_cred_get(), 0)))
555			return(EACCES);
556
557		ret = ddp_shutdown(*count_only);
558
559		if (*count_only != 0)
560		{
561			*count_only = ret;
562			return(0);
563		}
564		else
565		{
566			if (ret == 0)
567			{
568				/* AppleTalk was successfully shut down. Send event. */
569				atalk_post_msg(0, KEV_ATALK_DISABLED, 0, 0);
570				return 0;
571			}
572			else
573				return EBUSY;
574		}
575
576		break;
577	}
578
579	case SIOCSIFADDR:
580		/* check for root access */
581		if ((error = suser(kauth_cred_get(), 0)))
582			error = EACCES;
583		else if (ifID)
584			error = EEXIST;
585		else {
586			if (xpatcnt == 0) {
587				at_state.flags |= AT_ST_STARTING;
588				ddp_brt_init();
589			}
590
591			/* *** find an empty entry *** */
592			ifID = &at_interfaces[xpatcnt];
593			bzero((caddr_t)ifID, sizeof(at_ifaddr_t));
594			strlcpy(ifID->ifName, ifr->ifr_name, sizeof(ifID->ifName));
595
596			ifID->aa_ifp = ifp;
597			ifa = &ifID->aa_ifa;
598			error = at_domifattach(ifp, ifID);
599			if (error == EEXIST) {
600				ifID->at_was_attached = 1;
601				error = 0;
602			}
603			if (error != 0) {
604				break;
605			}
606			/* XXX ethernet-specific */
607			ifID->cable_multicast_addr = etalk_multicast_addr;
608			xpatcnt++;
609			ifnet_lock_exclusive(ifp);
610			/*
611			 * Holding ifnet lock here prevents the link address
612			 * from changing contents, so no need to hold the ifa
613			 * lock.  The link address is always present; it's
614			 * never freed.
615			 */
616			sdl = (struct sockaddr_dl *)ifp->if_lladdr->ifa_addr;
617			bcopy(LLADDR(sdl), ifID->xaddr, sizeof(ifID->xaddr));
618#ifdef APPLETALK_DEBUG
619			kprintf("SIOCSIFADDR: local enet address is "
620			    "%x.%x.%x.%x.%x.%x\n",
621			    ifID->xaddr[0], ifID->xaddr[1],
622			    ifID->xaddr[2], ifID->xaddr[3],
623			    ifID->xaddr[4], ifID->xaddr[5]);
624#endif
625
626			/* attach the AppleTalk address to the ifnet structure */
627			ifa = &ifID->aa_ifa;
628			ifa_lock_init(ifa);
629			VERIFY(!(ifa->ifa_debug & IFD_ALLOC));
630			ifa->ifa_addr = (struct sockaddr *)&ifID->ifNodeAddress;
631			ifID->ifNodeAddress.sat_len = sizeof(struct sockaddr_at);
632			ifID->ifNodeAddress.sat_family =  AF_APPLETALK;
633			/* the address itself will be filled in when ifThisNode
634			   is set */
635			IFA_LOCK(ifa);
636			if_attach_ifa(ifp, ifa);
637			/* add a reference for at_interfaces[] */
638			IFA_ADDREF_LOCKED(ifa);
639			IFA_UNLOCK(ifa);
640			ifnet_lock_done(ifp);
641		}
642	  break;
643
644	/* complete the initialization started in SIOCSIFADDR */
645	case AIOCSIFADDR:
646	{
647		at_if_cfg_t *cfgp = (at_if_cfg_t *)data;
648
649		if (!(at_state.flags & AT_ST_STARTING))
650			return(ENOTREADY);
651
652		if (!(ifID = find_ifID(cfgp->ifr_name)))
653			return(EINVAL);
654
655		return(lap_online(ifID, cfgp));
656		break;
657	}
658
659#ifdef NOT_YET
660	/* *** this can't be added until AT can handle dynamic addition and
661	       deletion of interfaces *** */
662	case SIOCDIFADDR:
663		/* check for root access */
664		if (error = suser(kauth_cred_get(), 0))
665			error = EACCES;
666		else if (!ifID)
667			error = EINVAL;
668		else
669			elap_offline(ifID);
670		break;
671#endif
672
673    case SIOCSETOT: {
674        struct atpcb	*at_pcb, *clonedat_pcb;
675        int				cloned_fd = *(int *)data;
676
677        at_pcb = sotoatpcb(so);
678
679        /* let's make sure it's either -1 or a valid file descriptor */
680        if (cloned_fd != -1) {
681            struct socket	*cloned_so;
682			error = file_socket(cloned_fd, &cloned_so);
683            if (error)
684                break;
685            clonedat_pcb = sotoatpcb(cloned_so);
686        } else {
687            clonedat_pcb = NULL;
688        }
689
690        if (clonedat_pcb == NULL) {
691            at_pcb->ddp_flags |= DDPFLG_STRIPHDR;
692        } else {
693            at_pcb->ddp_flags = clonedat_pcb->ddp_flags;
694        }
695		file_drop(cloned_fd);
696        break;
697    }
698
699	case SIOCPROTOATTACH:
700		/* check for root access */
701		if (suser(kauth_cred_get(), 0) != 0) {
702			error = EACCES;
703			break;
704		}
705		error = at_domifattach(ifp, ifID);
706		break;
707
708	case SIOCPROTODETACH:
709		/* check for root access */
710		if (suser(kauth_cred_get(), 0) != 0) {
711			error = EACCES;
712			break;
713		}
714		if (ifID != NULL) {
715			error = EBUSY;
716			break;
717		}
718		error = proto_unplumb(PF_APPLETALK, ifp);
719		break;
720
721	default:
722		if (ifp == 0 || ifp->if_ioctl == 0)
723			return (EOPNOTSUPP);
724		return ifnet_ioctl(ifp, 0, cmd, data);
725	}
726
727	return(error);
728}
729
730/* From dlil_post_msg() */
731void atalk_post_msg(struct ifnet *ifp, u_long event_code, struct at_addr *address, at_nvestr_t *zone)
732{
733	struct kev_atalk_data  	at_event_data;
734	struct kev_msg  		ev_msg;
735
736	bzero(&ev_msg, sizeof(struct kev_msg));
737	ev_msg.vendor_code    = KEV_VENDOR_APPLE;
738	ev_msg.kev_class      = KEV_NETWORK_CLASS;
739	ev_msg.kev_subclass   = KEV_ATALK_SUBCLASS;
740	ev_msg.event_code 	  = event_code;
741
742	bzero(&at_event_data, sizeof(struct kev_atalk_data));
743
744	if (ifp != 0) {
745		strlcpy(&at_event_data.link_data.if_name[0], ifp->if_name, IFNAMSIZ);
746		at_event_data.link_data.if_family = ifp->if_family;
747		at_event_data.link_data.if_unit   = (unsigned long) ifp->if_unit;
748	}
749
750	if (address != 0) {
751		at_event_data.node_data.address = *address;
752	}
753	else if (zone != 0) {
754		at_event_data.node_data.zone = *zone;
755	}
756
757	ev_msg.dv[0].data_length = sizeof(struct kev_atalk_data);
758	ev_msg.dv[0].data_ptr    = &at_event_data;
759	ev_msg.dv[1].data_length = 0;
760
761	kev_post_msg(&ev_msg);
762}
763
764
765/*
766 * This is untested; the code is here only for completeness.
767 */
768void
769at_purgeaddrs(struct ifnet *ifp)
770{
771	at_ifaddr_t *ifID = NULL;
772	int pat_id;
773
774        /* Find address for this interface, if it exists */
775	for (pat_id = 0; pat_id < xpatcnt; pat_id++) {
776		if (at_interfaces[pat_id].aa_ifp == ifp) {
777			ifID = &at_interfaces[pat_id];
778			elap_offline(ifID);
779		}
780	}
781}
782