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