xref: /original-bsd/sys/netiso/iso_snpac.c (revision c829ecf6)
1 /***********************************************************
2 		Copyright IBM Corporation 1987
3 
4                       All Rights Reserved
5 
6 Permission to use, copy, modify, and distribute this software and its
7 documentation for any purpose and without fee is hereby granted,
8 provided that the above copyright notice appear in all copies and that
9 both that copyright notice and this permission notice appear in
10 supporting documentation, and that the name of IBM not be
11 used in advertising or publicity pertaining to distribution of the
12 software without specific, written prior permission.
13 
14 IBM DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
15 ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
16 IBM BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
17 ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
18 WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
19 ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
20 SOFTWARE.
21 
22 ******************************************************************/
23 
24 /*
25  * ARGO Project, Computer Sciences Dept., University of Wisconsin - Madison
26  */
27 /* $Header: iso_snpac.c,v 1.8 88/09/19 13:51:36 hagens Exp $ */
28 /* $Source: /usr/argo/sys/netiso/RCS/iso_snpac.c,v $ */
29 /*	@(#)iso_snpac.c	7.7 (Berkeley) 06/09/90 */
30 
31 #ifndef lint
32 static char *rcsid = "$Header: iso_snpac.c,v 1.8 88/09/19 13:51:36 hagens Exp $";
33 #endif lint
34 
35 #ifdef ISO
36 
37 #include "types.h"
38 #include "param.h"
39 #include "systm.h"
40 #include "user.h"
41 #include "mbuf.h"
42 #include "domain.h"
43 #include "protosw.h"
44 #include "socket.h"
45 #include "socketvar.h"
46 #include "errno.h"
47 #include "ioctl.h"
48 #include "kernel.h"
49 
50 #include "../net/if.h"
51 #include "../net/if_dl.h"
52 #include "../net/route.h"
53 
54 #include "iso.h"
55 #include "iso_var.h"
56 #include "iso_snpac.h"
57 #include "clnp.h"
58 #include "clnp_stat.h"
59 #include "esis.h"
60 #include "argo_debug.h"
61 
62 #define	SNPAC_BSIZ	20		/* bucket size */
63 #define	SNPAC_NB	13		/* number of buckets */
64 #define	SNPAC_SIZE	(SNPAC_BSIZ * SNPAC_NB)
65 struct	snpa_cache	iso_snpac[SNPAC_SIZE];
66 u_int				iso_snpac_size = SNPAC_SIZE;/* for iso_map command */
67 int 				iso_systype = SNPA_ES;	/* default to be an ES */
68 
69 struct sockaddr_iso blank_siso = {sizeof(blank_siso), AF_ISO};
70 extern u_long iso_hashchar();
71 static struct sockaddr_iso
72 	dst	= {sizeof(dst), AF_ISO},
73 	gte	= {sizeof(dst), AF_ISO},
74 	src	= {sizeof(dst), AF_ISO},
75 	msk	= {sizeof(dst), AF_ISO},
76 	zmk = {1};
77 #define zsi blank_siso
78 #define zero_isoa	zsi.siso_addr
79 #define zap_isoaddr(a, b) (bzero((caddr_t)&a.siso_addr, sizeof(*r)), \
80 	   ((r = b) && bcopy((caddr_t)r, (caddr_t)&a.siso_addr, 1 + (r)->isoa_len)))
81 #define S(x) ((struct sockaddr *)&(x))
82 
83 static struct sockaddr_dl blank_dl = {sizeof(blank_dl), AF_LINK};
84 static struct sockaddr_dl gte_dl;
85 #define zap_linkaddr(a, b, c, i) \
86 	(*a = blank_dl, bcopy(b, a->sdl_data, a->sdl_alen = c), a->sdl_index = i)
87 
88 /*
89  *	We only keep track of a single IS at a time.
90  */
91 struct rtentry	*known_is;
92 
93 /*
94  *	Addresses taken from NBS agreements, December 1987.
95  *
96  *	These addresses assume on-the-wire transmission of least significant
97  *	bit first. This is the method used by 802.3. When these
98  *	addresses are passed to the token ring driver, (802.5), they
99  *	must be bit-swaped because 802.5 transmission order is MSb first.
100  *
101  *	Furthermore, according to IBM Austin, these addresses are not
102  *	true token ring multicast addresses. More work is necessary
103  *	to get multicast to work right on token ring.
104  *
105  *	Currently, the token ring driver does not handle multicast, so
106  *	these addresses are converted into the broadcast address in
107  *	lan_output() That means that if these multicast addresses change
108  *	the token ring driver must be altered.
109  */
110 struct snpa_cache	all_es = {
111 	{ { 0x0 },							/* sc_nsap */
112 	6,									/* sc_len */
113 	{ 0x09, 0x00, 0x2b, 0x00, 0x00, 0x04 }, /* sc_snpa */
114 	SNPA_VALID,							/* sc_flags */
115 	0	}								/* sc_ht */
116 };
117 struct snpa_cache	all_is = {
118 	{ { 0x0 },							/* sc_nsap */
119 	6,									/* sc_len */
120 	{ 0x09, 0x00, 0x2b, 0x00, 0x00, 0x05 }, /* sc_snpa */
121 	SNPA_VALID,							/* sc_flags */
122 	0	}								/* sc_ht */
123 };
124 char all_es_snpa[] = { 0x09, 0x00, 0x2b, 0x00, 0x00, 0x04 }; /* sc_snpa */
125 char all_is_snpa[] = { 0x09, 0x00, 0x2b, 0x00, 0x00, 0x05 }; /* sc_snpa */
126 
127 union sockunion {
128 	struct sockaddr_iso siso;
129 	struct sockaddr_dl	sdl;
130 	struct sockaddr		sa;
131 };
132 
133 /*
134  * FUNCTION:		llc_rtrequest
135  *
136  * PURPOSE:			Manage routing table entries specific to LLC for ISO.
137  *
138  * NOTES:			This does a lot of obscure magic;
139  */
140 llc_rtrequest(req, rt, sa)
141 int req;
142 register struct rtentry *rt;
143 struct sockaddr *sa;
144 {
145 	register union sockunion *gate = (union sockunion *)rt->rt_gateway;
146 	register struct llinfo_llc *lc = (struct llinfo_llc *)rt->rt_llinfo, *lc2;
147 	struct rtentry *rt2;
148 	struct ifnet *ifp = rt->rt_ifp;
149 	int addrlen = ifp->if_addrlen;
150 
151 	if (lc == 0)
152 		panic("llc_rtrequest 1");
153 	switch (req) {
154 	case RTM_RESOLVE:
155 		if (gate->sdl.sdl_family == AF_LINK) {
156 			/* We have just cloned a route to a host presumed to be on
157 			   our local net */
158 			gate->sdl.sdl_alen = 0;
159 		}
160 		/* Fall Through */
161 	case RTM_ADD:
162 		lc->lc_rt = rt;
163 		if (rt->rt_flags & RTF_CLONING) {
164 			/*
165 			 * Case 1: This route may come from a route to iface with mask
166 			 * or from a default IS.
167 			 */
168 			rt->rt_gateway = ifp->if_addrlist->ifa_addr;
169 		} else if ((rt->rt_flags & RTF_GATEWAY == 0) &&
170 				   gate->sdl.sdl_family == AF_LINK) {
171 			/*
172 			 * Case 2:  This route may come from ESIS or a manual route
173 			 * add with a LL address.
174 			 */
175 			insque(lc, &llinfo_llc);
176 			if (gate->sdl.sdl_alen == sizeof(struct esis_req) + addrlen) {
177 				gate->sdl.sdl_alen -= sizeof(struct esis_req);
178 				bcopy(addrlen + LLADDR(&gate->sdl),
179 					  (caddr_t)&lc->lc_er, sizeof(lc->lc_er));
180 			} else if (gate->sdl.sdl_alen == addrlen)
181 				lc->lc_flags = (SNPA_ES | SNPA_VALID | SNPA_PERM);
182 		} else {
183 			/*
184 			 * Case 3:  Told to add route via a gateway;
185 			 * try to provoke LL route to exist for gateway by cloning.
186 			 */
187 			if (gate->siso.siso_family == AF_ISO &&
188 				(rt->rt_flags & RTF_GATEWAY) &&
189 				(rt2 = rtalloc1(&gate->sa, 1)) &&
190 					(lc2 = (struct llinfo_llc *)rt2->rt_llinfo)) {
191 				lc->lc_rtgate = rt2;
192 				rt2->rt_use++;
193 				if (lc2->lc_rtgate == 0) {
194 					lc->lc_prev = lc->lc_next = lc;
195 					lc2->lc_rtgate = rt;
196 					rt->rt_use++;
197 				} else {
198 					if (lc2->lc_rtgate->rt_llinfo == 0)
199 						panic("llc_rtrequest 2");
200 					insque(lc, lc2->lc_rtgate->rt_llinfo);
201 				}
202 			}
203 		}
204 		break;
205 	case RTM_DELETE:
206 		if (rt->rt_flags & RTF_GATEWAY) {
207 			if (rt2 = lc->lc_rtgate) {
208 				if ((lc2 = (struct llinfo_llc *)rt2->rt_llinfo) &&
209 				    (lc2->lc_rtgate == rt)) {
210 						rt->rt_use--;
211 						lc2->lc_rtgate = (lc->lc_next != lc->lc_prev) ?
212 						   lc->lc_next->lc_rt : 0;
213 					}
214 					RTFREE(rt2);
215 			}
216 		} else {
217 			if ((rt2 = lc->lc_rtgate) &&
218 			    (lc2 == (struct llinfo_llc *)rt2->rt_llinfo)) {
219 					struct llinfo_llc *head = lc2;
220 					do {
221 						rt->rt_use--;
222 						lc2->lc_rtgate = 0;
223 						lc2 = lc2->lc_next;
224 					} while (lc2 != head);
225 			}
226 		}
227 		if (lc->lc_next)
228 			remque(lc);
229 		break;
230 	}
231 }
232 /*
233  * FUNCTION:		iso_snparesolve
234  *
235  * PURPOSE:			Resolve an iso address into snpa address
236  *
237  * RETURNS:			0 if addr is resolved
238  *					errno if addr is unknown
239  *
240  * SIDE EFFECTS:
241  *
242  * NOTES:			Now that we have folded the snpa cache into the routing
243  *					table, we know there is no snpa address known for this
244  *					destination.  If we know of a default IS, then the address
245  *					of the IS is returned.  If no IS is known, then return the
246  *					multi-cast address for "all ES" for this interface.
247  *
248  *					NB: the last case described above constitutes the
249  *					query configuration function 9542, sec 6.5
250  *					A mechanism is needed to prevent this function from
251  *					being invoked if the system is an IS.
252  */
253 iso_snparesolve(ifp, dest, snpa, snpa_len)
254 struct	ifnet *ifp;			/* outgoing interface */
255 struct	sockaddr_iso *dest;	/* destination */
256 caddr_t	snpa;				/* RESULT: snpa to be used */
257 int		*snpa_len;			/* RESULT: length of snpa */
258 {
259 	struct	llinfo_llc *sc;	/* ptr to snpa table entry */
260 	caddr_t	found_snpa;
261 	int 	addrlen;
262 
263 	/*
264 	 *	This hack allows us to send esis packets that have the destination snpa
265 	 *	addresss embedded in the destination nsap address
266 	 */
267 	if (dest->siso_data[0] == AFI_SNA) {
268 		/*
269 		 *	This is a subnetwork address. Return it immediately
270 		 */
271 		IFDEBUG(D_SNPA)
272 			printf("iso_snparesolve: return SN address\n");
273 		ENDDEBUG
274 		addrlen = dest->siso_nlen - 1;	/* subtract size of AFI */
275 		found_snpa = (caddr_t) dest->siso_data + 1;
276 	/*
277 	 * If we are an IS, we can't do much with the packet;
278 	 *	Check if we know about an IS.
279 	 */
280 	} else if (iso_systype != SNPA_IS && known_is != 0 &&
281 				(sc = (struct llinfo_llc *)known_is->rt_llinfo) &&
282 				 (sc->lc_flags & SNPA_VALID)) {
283 		register struct sockaddr_dl *sdl =
284 			(struct sockaddr_dl *)(known_is->rt_gateway);
285 		found_snpa = LLADDR(sdl);
286 		addrlen = sdl->sdl_alen;
287 	} else if (ifp->if_flags & IFF_BROADCAST) {
288 		/*
289 		 *	no IS, no match. Return "all es" multicast address for this
290 		 *	interface, as per Query Configuration Function (9542 sec 6.5)
291 		 *
292 		 *	Note: there is a potential problem here. If the destination
293 		 *	is on the subnet and it does not respond with a ESH, but
294 		 *	does send back a TP CC, a connection could be established
295 		 *	where we always transmit the CLNP packet to "all es"
296 		 */
297 		addrlen = ifp->if_addrlen;
298 		found_snpa = (caddr_t)all_es_snpa;
299 	} else
300 		return (ENETUNREACH);
301 	bcopy(found_snpa, snpa, *snpa_len = addrlen);
302 	return (0);
303 }
304 
305 
306 /*
307  * FUNCTION:		snpac_free
308  *
309  * PURPOSE:			free an entry in the iso address map table
310  *
311  * RETURNS:			nothing
312  *
313  * SIDE EFFECTS:
314  *
315  * NOTES:			If there is a route entry associated with cache
316  *					entry, then delete that as well
317  */
318 snpac_free(lc)
319 register struct llinfo_llc *lc;		/* entry to free */
320 {
321 	register struct rtentry *rt = lc->lc_rt;
322 	register struct iso_addr *r;
323 
324 	if (known_is == rt)
325 		known_is = 0;
326 	if (rt && (rt->rt_flags & RTF_UP) &&
327 		(rt->rt_flags & (RTF_DYNAMIC | RTF_MODIFIED))) {
328 			RTFREE(rt);
329 			rtrequest(RTM_DELETE, rt_key(rt), rt->rt_gateway, rt_mask(rt),
330 						rt->rt_flags, (struct rtentry **)0);
331 		RTFREE(rt);
332 	}
333 }
334 
335 /*
336  * FUNCTION:		snpac_add
337  *
338  * PURPOSE:			Add an entry to the snpa cache
339  *
340  * RETURNS:
341  *
342  * SIDE EFFECTS:
343  *
344  * NOTES:			If entry already exists, then update holding time.
345  */
346 snpac_add(ifp, nsap, snpa, type, ht)
347 struct ifnet		*ifp;		/* interface info is related to */
348 struct iso_addr		*nsap;		/* nsap to add */
349 caddr_t				snpa;		/* translation */
350 char				type;		/* SNPA_IS or SNPA_ES */
351 u_short				ht;			/* holding time (in seconds) */
352 {
353 	register struct	llinfo_llc *lc;
354 	struct	rtentry *rt;
355 	register struct	iso_addr *r; /* for zap_isoaddr macro */
356 	int		snpalen = min(ifp->if_addrlen, MAX_SNPALEN);
357 	int		new_entry = 0, index = ifp->if_index;
358 
359 	zap_isoaddr(dst, nsap);
360 	rt = rtalloc1(S(dst), 0);
361 	if (rt == 0) {
362 		new_entry = 1;
363 		zap_linkaddr((&gte_dl), snpa, snpalen, index);
364 		if (rtrequest(RTM_ADD, S(dst), S(gte_dl), (struct sockaddr *)0,
365 						RTF_UP | RTF_HOST, &rt) || rt == 0)
366 			return (0);
367 	} else {
368 		register struct sockaddr_dl *sdl = (struct sockaddr_dl *)rt->rt_gateway;
369 		if (sdl->sdl_family != AF_LINK || sdl->sdl_alen == 0) {
370 			int old_sdl_len = sdl->sdl_len;
371 			if (old_sdl_len < sizeof(*sdl))
372 				return (0);
373 			zap_linkaddr(sdl, snpa, snpalen, index);
374 			sdl->sdl_len = old_sdl_len;
375 			new_entry = 1;
376 		}
377 	}
378 	lc = (struct llinfo_llc *)rt->rt_llinfo;
379 	lc->lc_ht = ht;
380 	lc->lc_flags = SNPA_VALID | type;
381 	if (type & SNPA_IS)
382 		snpac_logdefis(rt);
383 }
384 
385 /*
386  * FUNCTION:		snpac_ioctl
387  *
388  * PURPOSE:			Set/Get the system type and esis parameters
389  *
390  * RETURNS:			0 on success, or unix error code
391  *
392  * SIDE EFFECTS:
393  *
394  * NOTES:
395  */
396 snpac_ioctl (cmd, data)
397 int		cmd;	/* ioctl to process */
398 caddr_t	data;	/* data for the cmd */
399 {
400 	register struct systype_req *rq = (struct systype_req *)data;
401 	extern short	esis_holding_time, esis_config_time;
402 
403 	IFDEBUG (D_IOCTL)
404 		if (cmd == SIOCSSTYPE)
405 			printf("snpac_ioctl: cmd set, type x%x, ht %d, ct %d\n",
406 				rq->sr_type, rq->sr_holdt, rq->sr_configt);
407 		else
408 			printf("snpac_ioctl: cmd get\n");
409 	ENDDEBUG
410 
411 	if (cmd == SIOCSSTYPE) {
412 		if (suser(u.u_cred, &u.u_acflag))
413 			return(EACCES);
414 		if ((rq->sr_type & (SNPA_ES|SNPA_IS)) == (SNPA_ES|SNPA_IS))
415 			return(EINVAL);
416 		if (rq->sr_type & SNPA_ES) {
417 			iso_systype = SNPA_ES;
418 		} else if (rq->sr_type & SNPA_IS) {
419 			iso_systype = SNPA_IS;
420 		} else {
421 			return(EINVAL);
422 		}
423 		esis_holding_time = rq->sr_holdt;
424 		esis_config_time = rq->sr_configt;
425 	} else if (cmd == SIOCGSTYPE) {
426 		rq->sr_type = iso_systype;
427 		rq->sr_holdt = esis_holding_time;
428 		rq->sr_configt = esis_config_time;
429 	} else {
430 		return (EINVAL);
431 	}
432 	return (0);
433 }
434 
435 /*
436  * FUNCTION:		snpac_logdefis
437  *
438  * PURPOSE:			Mark the IS passed as the default IS
439  *
440  * RETURNS:			nothing
441  *
442  * SIDE EFFECTS:
443  *
444  * NOTES:
445  */
446 snpac_logdefis(sc)
447 register struct rtentry *sc;
448 {
449 	register struct iso_addr *r;
450 	register struct sockaddr_dl *sdl = (struct sockaddr_dl *)sc->rt_gateway;
451 	register struct rtentry *rt = rtalloc1((struct sockaddr *)&zsi, 0);
452 
453 	zap_linkaddr((&gte_dl), LLADDR(sdl), sdl->sdl_alen, sdl->sdl_index);
454 	if (known_is == 0)
455 		known_is = sc;
456 	if (known_is != sc) {
457 		rtfree(known_is);
458 		known_is = sc;
459 	}
460 	if (rt == 0) {
461 		rtrequest(RTM_ADD, S(zsi), S(gte_dl), S(zmk),
462 						RTF_DYNAMIC|RTF_GATEWAY|RTF_CLONING, 0);
463 		return;
464 	}
465 	if (rt->rt_flags & (RTF_DYNAMIC | RTF_MODIFIED)) {
466 		*((struct sockaddr_dl *)rt->rt_gateway) = gte_dl;
467 	}
468 }
469 
470 /*
471  * FUNCTION:		snpac_age
472  *
473  * PURPOSE:			Time out snpac entries
474  *
475  * RETURNS:
476  *
477  * SIDE EFFECTS:
478  *
479  * NOTES:			When encountering an entry for the first time, snpac_age
480  *					may delete up to SNPAC_AGE too many seconds. Ie.
481  *					if the entry is added a moment before snpac_age is
482  *					called, the entry will immediately have SNPAC_AGE
483  *					seconds taken off the holding time, even though
484  *					it has only been held a brief moment.
485  *
486  *					The proper way to do this is set an expiry timeval
487  *					equal to current time + holding time. Then snpac_age
488  *					would time out entries where expiry date is older
489  *					than the current time.
490  */
491 snpac_age()
492 {
493 	register struct llinfo_llc	*lc;
494 
495 	timeout(snpac_age, (caddr_t)0, SNPAC_AGE * hz);
496 
497 	for (lc = llinfo_llc.lc_next; lc != & llinfo_llc; lc = lc->lc_next) {
498 		if (((lc->lc_flags & SNPA_PERM) == 0) && (lc->lc_flags & SNPA_VALID)) {
499 			lc->lc_ht -= SNPAC_AGE;
500 			if (lc->lc_ht > 0)
501 				continue;
502 			else
503 				snpac_free(lc);
504 		}
505 	}
506 }
507 
508 /*
509  * FUNCTION:		snpac_ownmulti
510  *
511  * PURPOSE:			Determine if the snpa address is a multicast address
512  *					of the same type as the system.
513  *
514  * RETURNS:			true or false
515  *
516  * SIDE EFFECTS:
517  *
518  * NOTES:			Used by interface drivers when not in eavesdrop mode
519  *					as interm kludge until
520  *					real multicast addresses can be configured
521  */
522 snpac_ownmulti(snpa, len)
523 caddr_t	snpa;
524 u_int	len;
525 {
526 	return (((iso_systype & SNPA_ES) &&
527 			 (!bcmp(snpa, (caddr_t)all_es_snpa, len))) ||
528 			((iso_systype & SNPA_IS) &&
529 			 (!bcmp(snpa, (caddr_t)all_is_snpa, len))));
530 }
531 
532 /*
533  * FUNCTION:		snpac_flushifp
534  *
535  * PURPOSE:			Flush entries associated with specific ifp
536  *
537  * RETURNS:			nothing
538  *
539  * SIDE EFFECTS:
540  *
541  * NOTES:
542  */
543 snpac_flushifp(ifp)
544 struct ifnet	*ifp;
545 {
546 	register struct llinfo_llc	*lc;
547 
548 	for (lc = llinfo_llc.lc_next; lc != & llinfo_llc; lc = lc->lc_next) {
549 		if (lc->lc_rt->rt_ifp == ifp && (lc->lc_flags & SNPA_VALID))
550 			snpac_free(lc);
551 	}
552 }
553 
554 /*
555  * FUNCTION:		snpac_rtrequest
556  *
557  * PURPOSE:			Make a routing request
558  *
559  * RETURNS:			nothing
560  *
561  * SIDE EFFECTS:
562  *
563  * NOTES:			In the future, this should make a request of a user
564  *					level routing daemon.
565  */
566 snpac_rtrequest(req, host, gateway, netmask, flags, ret_nrt)
567 int				req;
568 struct iso_addr	*host;
569 struct iso_addr	*gateway;
570 struct iso_addr	*netmask;
571 short			flags;
572 struct rtentry	**ret_nrt;
573 {
574 	register struct iso_addr *r;
575 
576 	IFDEBUG(D_SNPA)
577 		printf("snpac_rtrequest: ");
578 		if (req == RTM_ADD)
579 			printf("add");
580 		else if (req == RTM_DELETE)
581 			printf("delete");
582 		else
583 			printf("unknown command");
584 		printf(" dst: %s\n", clnp_iso_addrp(host));
585 		printf("\tgateway: %s\n", clnp_iso_addrp(gateway));
586 	ENDDEBUG
587 
588 
589 	zap_isoaddr(dst, host);
590 	zap_isoaddr(gte, gateway);
591 	zap_isoaddr(msk, netmask);
592 
593 	rtrequest(req, S(dst), S(gte), (netmask ? S(msk) : (struct sockaddr *)0),
594 		flags, ret_nrt);
595 }
596 
597 /*
598  * FUNCTION:		snpac_addrt
599  *
600  * PURPOSE:			Associate a routing entry with an snpac entry
601  *
602  * RETURNS:			nothing
603  *
604  * SIDE EFFECTS:
605  *
606  * NOTES:			If a cache entry exists for gateway, then
607  *					make a routing entry (host, gateway) and associate
608  *					with gateway.
609  *
610  *					If a route already exists and is different, first delete
611  *					it.
612  *
613  *					This could be made more efficient by checking
614  *					the existing route before adding a new one.
615  */
616 snpac_addrt(ifp, host, gateway, netmask)
617 struct ifnet *ifp;
618 struct iso_addr	*host, *gateway, *netmask;
619 {
620 	register struct iso_addr *r;
621 
622 	zap_isoaddr(dst, host);
623 	zap_isoaddr(gte, gateway);
624 	zap_isoaddr(msk, netmask);
625 	if (netmask) {
626 		rtredirect(S(dst), S(gte), S(msk), RTF_DONE, S(gte), 0);
627 	} else
628 		rtredirect(S(dst), S(gte), (struct sockaddr *)0,
629 							RTF_DONE | RTF_HOST, S(gte), 0);
630 }
631 #endif	ISO
632