Annotation of embedaddon/pimdd/route.c, revision 1.1.1.1
1.1 misho 1: /*
2: * Copyright (c) 1998 by the University of Oregon.
3: * All rights reserved.
4: *
5: * Permission to use, copy, modify, and distribute this software and
6: * its documentation in source and binary forms for lawful
7: * purposes and without fee is hereby granted, provided
8: * that the above copyright notice appear in all copies and that both
9: * the copyright notice and this permission notice appear in supporting
10: * documentation, and that any documentation, advertising materials,
11: * and other materials related to such distribution and use acknowledge
12: * that the software was developed by the University of Oregon.
13: * The name of the University of Oregon may not be used to endorse or
14: * promote products derived from this software without specific prior
15: * written permission.
16: *
17: * THE UNIVERSITY OF OREGON DOES NOT MAKE ANY REPRESENTATIONS
18: * ABOUT THE SUITABILITY OF THIS SOFTWARE FOR ANY PURPOSE. THIS SOFTWARE IS
19: * PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES,
20: * INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
21: * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, TITLE, AND
22: * NON-INFRINGEMENT.
23: *
24: * IN NO EVENT SHALL UO, OR ANY OTHER CONTRIBUTOR BE LIABLE FOR ANY
25: * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES, WHETHER IN CONTRACT,
26: * TORT, OR OTHER FORM OF ACTION, ARISING OUT OF OR IN CONNECTION WITH,
27: * THE USE OR PERFORMANCE OF THIS SOFTWARE.
28: *
29: * Other copyrights might apply to parts of this software and are so
30: * noted when applicable.
31: */
32: /*
33: * Questions concerning this software should be directed to
34: * Kurt Windisch (kurtw@antc.uoregon.edu)
35: *
36: * $Id: route.c,v 1.27 1998/12/30 20:26:21 kurtw Exp $
37: */
38: /*
39: * Part of this program has been derived from PIM sparse-mode pimd.
40: * The pimd program is covered by the license in the accompanying file
41: * named "LICENSE.pimd".
42: *
43: * The pimd program is COPYRIGHT 1998 by University of Southern California.
44: *
45: * Part of this program has been derived from mrouted.
46: * The mrouted program is covered by the license in the accompanying file
47: * named "LICENSE.mrouted".
48: *
49: * The mrouted program is COPYRIGHT 1989 by The Board of Trustees of
50: * Leland Stanford Junior University.
51: *
52: */
53:
54: #include "defs.h"
55:
56:
57: static void process_cache_miss __P((struct igmpmsg *igmpctl));
58: static void process_wrong_iif __P((struct igmpmsg *igmpctl));
59:
60: u_int32 default_source_preference = DEFAULT_LOCAL_PREF;
61: u_int32 default_source_metric = DEFAULT_LOCAL_METRIC;
62:
63: /* Return the iif for given address */
64: vifi_t
65: get_iif(address)
66: u_int32 address;
67: {
68: struct rpfctl rpfc;
69:
70: k_req_incoming(address, &rpfc);
71: if (rpfc.rpfneighbor.s_addr == INADDR_ANY_N)
72: return (NO_VIF);
73: return (rpfc.iif);
74: }
75:
76: /* Return the PIM neighbor toward a source */
77: /* If route not found or if a local source or if a directly connected source,
78: * but is not PIM router, or if the first hop router is not a PIM router,
79: * then return NULL.
80: */
81: pim_nbr_entry_t *
82: find_pim_nbr(source)
83: u_int32 source;
84: {
85: struct rpfctl rpfc;
86: pim_nbr_entry_t *pim_nbr;
87: u_int32 next_hop_router_addr;
88:
89: if (local_address(source) != NO_VIF)
90: return (pim_nbr_entry_t *)NULL;
91: k_req_incoming(source, &rpfc);
92: if ((rpfc.rpfneighbor.s_addr == INADDR_ANY_N)
93: || (rpfc.iif == NO_VIF))
94: return (pim_nbr_entry_t *)NULL;
95: next_hop_router_addr = rpfc.rpfneighbor.s_addr;
96: for (pim_nbr = uvifs[rpfc.iif].uv_pim_neighbors;
97: pim_nbr != (pim_nbr_entry_t *)NULL;
98: pim_nbr = pim_nbr->next)
99: if (pim_nbr->address == next_hop_router_addr)
100: return(pim_nbr);
101: return (pim_nbr_entry_t *)NULL;
102: }
103:
104:
105: /* TODO: check again the exact setup if the source is local or directly
106: * connected!!!
107: */
108: /* TODO: XXX: change the metric and preference for all (S,G) entries per
109: * source?
110: */
111: /* PIMDM TODO - If possible, this would be the place to correct set the
112: * source's preference and metric to that obtained from the kernel
113: * and/or unicast routing protocol. For now, set it to the configured
114: * default for local pref/metric.
115: */
116: /*
117: * Set the iif, upstream router, preference and metric for the route
118: * toward the source. Return TRUE is the route was found, othewise FALSE.
119: * If srctype==PIM_IIF_SOURCE and if the source is directly connected
120: * then the "upstream" is set to NULL.
121: * Note that srctype is a hold-over from the PIM-SM daemon and is unused.
122: */
123: int
124: set_incoming(srcentry_ptr, srctype)
125: srcentry_t *srcentry_ptr;
126: int srctype;
127: {
128: struct rpfctl rpfc;
129: u_int32 source = srcentry_ptr->address;
130: u_int32 neighbor_addr;
131: register struct uvif *v;
132: register pim_nbr_entry_t *n;
133:
134: /* Preference will be 0 if directly connected */
135: srcentry_ptr->preference = 0;
136: srcentry_ptr->metric = 0;
137:
138: if ((srcentry_ptr->incoming = local_address(source)) != NO_VIF) {
139: /* The source is a local address */
140: /* TODO: set the upstream to myself? */
141: srcentry_ptr->upstream = (pim_nbr_entry_t *)NULL;
142: return (TRUE);
143: }
144:
145: if ((srcentry_ptr->incoming = find_vif_direct(source)) == NO_VIF) {
146: /* TODO: probably need to check the case if the iif is disabled */
147: /* Use the lastest resource: the kernel unicast routing table */
148: k_req_incoming(source, &rpfc);
149: if ((rpfc.iif == NO_VIF) ||
150: rpfc.rpfneighbor.s_addr == INADDR_ANY_N) {
151: /* couldn't find a route */
152: IF_DEBUG(DEBUG_PIM_MRT | DEBUG_RPF)
153: log(LOG_DEBUG, 0, "NO ROUTE found for %s",
154: inet_fmt(source, s1));
155: return(FALSE);
156: }
157: srcentry_ptr->incoming = rpfc.iif;
158: neighbor_addr = rpfc.rpfneighbor.s_addr;
159: }
160: else {
161: /* The source is directly connected.
162: */
163: srcentry_ptr->upstream = (pim_nbr_entry_t *)NULL;
164: return (TRUE);
165: }
166:
167: /* set the preference for sources that aren't directly connected. */
168: v = &uvifs[srcentry_ptr->incoming];
169: srcentry_ptr->preference = v->uv_local_pref;
170: srcentry_ptr->metric = v->uv_local_metric;
171:
172: /*
173: * The upstream router must be a (PIM router) neighbor, otherwise we
174: * are in big trouble ;-)
175: */
176: for (n = v->uv_pim_neighbors; n != NULL; n = n->next) {
177: if (ntohl(neighbor_addr) < ntohl(n->address))
178: continue;
179: if (neighbor_addr == n->address) {
180: /*
181: *The upstream router is found in the list of neighbors.
182: * We are safe!
183: */
184: srcentry_ptr->upstream = n;
185: IF_DEBUG(DEBUG_RPF)
186: log(LOG_DEBUG, 0,
187: "For src %s, iif is %d, next hop router is %s",
188: inet_fmt(source, s1), srcentry_ptr->incoming,
189: inet_fmt(neighbor_addr, s2));
190: return(TRUE);
191: }
192: else break;
193: }
194:
195: /* TODO: control the number of messages! */
196: log(LOG_INFO, 0,
197: "For src %s, iif is %d, next hop router is %s: NOT A PIM ROUTER",
198: inet_fmt(source, s1), srcentry_ptr->incoming,
199: inet_fmt(neighbor_addr, s2));
200: srcentry_ptr->upstream = (pim_nbr_entry_t *)NULL;
201:
202: return(FALSE);
203: }
204:
205:
206: /* Set the leaves in a new mrtentry */
207: void set_leaves(mrtentry_ptr)
208: mrtentry_t *mrtentry_ptr;
209: {
210: vifi_t vifi;
211: struct uvif *v;
212:
213: /* Check for a group report on each vif */
214: for (vifi = 0, v = uvifs; vifi < numvifs; ++vifi, ++v)
215: if(check_grp_membership(v, mrtentry_ptr->group->group))
216: VIFM_SET(vifi, mrtentry_ptr->leaves);
217: }
218:
219:
220: /* Handle new receiver
221: *
222: * TODO: XXX: currently `source` is not used. Will be used with IGMPv3 where
223: * we have source-specific Join/Prune.
224: */
225: void
226: add_leaf(vifi, source, group)
227: vifi_t vifi;
228: u_int32 source;
229: u_int32 group;
230: {
231: grpentry_t *grpentry_ptr;
232: mrtentry_t *mrtentry_srcs;
233: vifbitmap_t new_leaves;
234: int state_change;
235:
236: grpentry_ptr = find_group(group);
237: if (grpentry_ptr == (grpentry_t *)NULL)
238: return;
239:
240: /* walk the source list for the group and add vif to oiflist */
241: for (mrtentry_srcs = grpentry_ptr->mrtlink;
242: mrtentry_srcs != (mrtentry_t *)NULL;
243: mrtentry_srcs = mrtentry_srcs->grpnext) {
244:
245: /* if applicable, add the vif to the leaves */
246: if (mrtentry_srcs->incoming == vifi)
247: continue;
248:
249: if(!(VIFM_ISSET(vifi, mrtentry_srcs->leaves))) {
250:
251: IF_DEBUG(DEBUG_MRT)
252: log(LOG_DEBUG, 0, "Adding leaf vif %d for src %s group %s",
253: vifi,
254: inet_fmt(mrtentry_srcs->source->address, s1),
255: inet_fmt(group, s2));
256:
257: VIFM_COPY(mrtentry_srcs->leaves, new_leaves);
258: VIFM_SET(vifi, new_leaves); /* Add the leaf */
259:
260: state_change =
261: change_interfaces(mrtentry_srcs,
262: mrtentry_srcs->incoming,
263: mrtentry_srcs->pruned_oifs,
264: new_leaves);
265:
266: /* Handle transition from negative cache */
267: if(state_change == 1)
268: trigger_join_alert(mrtentry_srcs);
269: }
270: }
271: }
272:
273:
274: /*
275: * TODO: XXX: currently `source` is not used. To be used with IGMPv3 where
276: * we have source-specific joins/prunes.
277: */
278: void
279: delete_leaf(vifi, source, group)
280: vifi_t vifi;
281: u_int32 source;
282: u_int32 group;
283: {
284: grpentry_t *grpentry_ptr;
285: mrtentry_t *mrtentry_srcs;
286: vifbitmap_t new_leaves;
287: int state_change;
288:
289: /* mrtentry_t *mrtentry_ptr;
290: * mrtentry_t *mrtentry_srcs;
291: * vifbitmap_t new_oifs;
292: * vifbitmap_t old_oifs;
293: * vifbitmap_t new_leaves;
294: */
295:
296: grpentry_ptr = find_group(group);
297: if (grpentry_ptr == (grpentry_t *)NULL)
298: return;
299:
300: /* walk the source list for the group and delete vif to leaves */
301: for (mrtentry_srcs = grpentry_ptr->mrtlink;
302: mrtentry_srcs != (mrtentry_t *)NULL;
303: mrtentry_srcs = mrtentry_srcs->grpnext) {
304:
305: /* if applicable, delete the vif from the leaves */
306: if (mrtentry_srcs->incoming == vifi)
307: continue;
308:
309: if(VIFM_ISSET(vifi, mrtentry_srcs->leaves)) {
310:
311: IF_DEBUG(DEBUG_MRT)
312: log(LOG_DEBUG, 0, "Deleting leaf vif %d for src %s, group %s",
313: vifi,
314: inet_fmt(mrtentry_srcs->source->address, s1),
315: inet_fmt(group, s2));
316:
317: VIFM_COPY(mrtentry_srcs->leaves, new_leaves);
318: VIFM_CLR(vifi, new_leaves); /* Remove the leaf */
319:
320: state_change =
321: change_interfaces(mrtentry_srcs,
322: mrtentry_srcs->incoming,
323: mrtentry_srcs->pruned_oifs,
324: new_leaves);
325:
326: /* Handle transition to negative cache */
327: if(state_change == -1)
328: trigger_prune_alert(mrtentry_srcs);
329: }
330: }
331: }
332:
333: void
334: calc_oifs(mrtentry_ptr, oifs_ptr)
335: mrtentry_t *mrtentry_ptr;
336: vifbitmap_t *oifs_ptr;
337: {
338: vifbitmap_t oifs;
339:
340: /*
341: * oifs =
342: * ((nbr_ifs - my_prune) + my_leaves) - incoming_interface,
343: * i.e. `leaves` have higher priority than `prunes`.
344: * Asserted oifs (those that lost assert) are handled as pruned oifs.
345: * The incoming interface is always deleted from the oifs
346: */
347:
348: if (mrtentry_ptr == (mrtentry_t *)NULL) {
349: VIFM_CLRALL(*oifs_ptr);
350: return;
351: }
352:
353: VIFM_COPY(nbr_vifs, oifs);
354: VIFM_CLR_MASK(oifs, mrtentry_ptr->pruned_oifs);
355: VIFM_MERGE(oifs, mrtentry_ptr->leaves, oifs);
356: VIFM_CLR(mrtentry_ptr->incoming, oifs);
357: VIFM_COPY(oifs, *oifs_ptr);
358: }
359:
360:
361: /*
362: * Set the iif, join/prune/leaves/asserted interfaces. Calculate and
363: * set the oifs.
364: * Return 1 if oifs change from NULL to not-NULL.
365: * Return -1 if oifs change from non-NULL to NULL
366: * else return 0
367: * If the iif change or if the oifs change from NULL to non-NULL
368: * or vice-versa, then schedule that mrtentry join/prune timer to
369: * timeout immediately.
370: */
371: int
372: change_interfaces(mrtentry_ptr, new_iif, new_pruned_oifs,
373: new_leaves_)
374: mrtentry_t *mrtentry_ptr;
375: vifi_t new_iif;
376: vifbitmap_t new_pruned_oifs;
377: vifbitmap_t new_leaves_;
378: {
379: vifbitmap_t old_pruned_oifs;
380: vifbitmap_t old_leaves;
381: vifbitmap_t new_leaves;
382: vifbitmap_t new_real_oifs; /* The result oifs */
383: vifbitmap_t old_real_oifs;
384: vifi_t old_iif;
385: int return_value;
386:
387: if (mrtentry_ptr == (mrtentry_t *)NULL)
388: return (0);
389:
390: VIFM_COPY(new_leaves_, new_leaves);
391:
392: old_iif = mrtentry_ptr->incoming;
393: VIFM_COPY(mrtentry_ptr->leaves, old_leaves);
394: VIFM_COPY(mrtentry_ptr->pruned_oifs, old_pruned_oifs);
395:
396: VIFM_COPY(mrtentry_ptr->oifs, old_real_oifs);
397:
398: mrtentry_ptr->incoming = new_iif;
399: VIFM_COPY(new_pruned_oifs, mrtentry_ptr->pruned_oifs);
400: VIFM_COPY(new_leaves, mrtentry_ptr->leaves);
401: calc_oifs(mrtentry_ptr, &new_real_oifs);
402:
403: if (VIFM_ISEMPTY(old_real_oifs)) {
404: if (VIFM_ISEMPTY(new_real_oifs))
405: return_value = 0;
406: else
407: return_value = 1;
408: } else {
409: if (VIFM_ISEMPTY(new_real_oifs))
410: return_value = -1;
411: else
412: return_value = 0;
413: }
414:
415: if ((VIFM_SAME(new_real_oifs, old_real_oifs))
416: && (new_iif == old_iif))
417: return 0; /* Nothing to change */
418:
419: VIFM_COPY(new_real_oifs, mrtentry_ptr->oifs);
420:
421: k_chg_mfc(igmp_socket, mrtentry_ptr->source->address,
422: mrtentry_ptr->group->group, new_iif, new_real_oifs);
423:
424: #ifdef RSRR
425: rsrr_cache_send(mrtentry_ptr, RSRR_NOTIFICATION_OK);
426: #endif /* RSRR */
427:
428: return (return_value);
429: }
430:
431:
432: /* TODO: implement it. Required to allow changing of the physical interfaces
433: * configuration without need to restart pimd.
434: */
435: int
436: delete_vif_from_mrt(vifi)
437: vifi_t vifi;
438: {
439: return TRUE;
440: }
441:
442:
443: static u_int16
444: max_prune_timeout(mrtentry_ptr)
445: mrtentry_t *mrtentry_ptr;
446: {
447: vifi_t vifi;
448: u_int16 time_left, max_holdtime = 0;
449:
450: for(vifi=0; vifi < numvifs; ++vifi)
451: if(VIFM_ISSET(vifi, mrtentry_ptr->pruned_oifs))
452: IF_TIMER_SET(mrtentry_ptr->prune_timers[vifi])
453: /* XXX - too expensive ? */
454: /* XXX: TIMER implem. dependency! */
455: if(mrtentry_ptr->prune_timers[vifi] > max_holdtime)
456: max_holdtime = time_left;
457:
458: if(max_holdtime == 0)
459: max_holdtime = (u_int16)PIM_JOIN_PRUNE_HOLDTIME;
460:
461: return(max_holdtime);
462: }
463:
464:
465: void process_kernel_call()
466: {
467: register struct igmpmsg *igmpctl; /* igmpmsg control struct */
468:
469: igmpctl = (struct igmpmsg *) igmp_recv_buf;
470:
471: switch (igmpctl->im_msgtype) {
472: case IGMPMSG_NOCACHE:
473: process_cache_miss(igmpctl);
474: break;
475: case IGMPMSG_WRONGVIF:
476: process_wrong_iif(igmpctl);
477: break;
478: default:
479: IF_DEBUG(DEBUG_KERN)
480: log(LOG_DEBUG, 0, "Unknown kernel_call code");
481: break;
482: }
483: }
484:
485:
486: /*
487: * Protocol actions:
488: * 1. Create (S,G) entry (find_route(CREATE))
489: * a. set iif and oifs
490: */
491: static void
492: process_cache_miss(igmpctl)
493: struct igmpmsg *igmpctl;
494: {
495: u_int32 source;
496: u_int32 group;
497: mrtentry_t *mrtentry_ptr;
498:
499: /*
500: * When there is a cache miss, we check only the header of the packet
501: * (and only it should be sent up by the kernel.
502: */
503:
504: group = igmpctl->im_dst.s_addr;
505: source = igmpctl->im_src.s_addr;
506:
507: IF_DEBUG(DEBUG_MFC)
508: log(LOG_DEBUG, 0, "Cache miss, src %s, dst %s",
509: inet_fmt(source, s1), inet_fmt(group, s2));
510:
511: /* Don't create routing entries for the LAN scoped addresses */
512: if (ntohl(group) <= INADDR_MAX_LOCAL_GROUP)
513: return;
514:
515: /* Create the (S,G) entry */
516: mrtentry_ptr = find_route(source, group, MRTF_SG, CREATE);
517: if (mrtentry_ptr == (mrtentry_t *)NULL)
518: return;
519: mrtentry_ptr->flags &= ~MRTF_NEW;
520:
521: /* Initialize Entry Timer */
522: SET_TIMER(mrtentry_ptr->timer, PIM_DATA_TIMEOUT);
523:
524: /* Set oifs */
525: set_leaves(mrtentry_ptr);
526: calc_oifs(mrtentry_ptr, &(mrtentry_ptr->oifs));
527:
528: /* Add it to the kernel */
529: k_chg_mfc(igmp_socket, source, group, mrtentry_ptr->incoming,
530: mrtentry_ptr->oifs);
531:
532: #ifdef RSRR
533: rsrr_cache_send(mrtentry_ptr, RSRR_NOTIFICATION_OK);
534: #endif /* RSRR */
535:
536: /* No need to call change_interfaces, but check for NULL oiflist */
537: if(VIFM_ISEMPTY(mrtentry_ptr->oifs))
538: trigger_prune_alert(mrtentry_ptr);
539: }
540:
541:
542: /*
543: * A multicast packet has been received on wrong iif by the kernel.
544: * If the packet was received on a point-to-point interface, rate-limit
545: * prunes. if the packet was received on a LAN interface, rate-limit
546: * asserts.
547: */
548: static void
549: process_wrong_iif(igmpctl)
550: struct igmpmsg *igmpctl;
551: {
552: u_int32 source;
553: u_int32 group;
554: vifi_t vifi;
555: mrtentry_t *mrtentry_ptr;
556:
557: group = igmpctl->im_dst.s_addr;
558: source = igmpctl->im_src.s_addr;
559: vifi = igmpctl->im_vif;
560:
561: /* PIMDM TODO Don't create routing entries for the LAN scoped addresses */
562: if (ntohl(group) <= INADDR_MAX_LOCAL_GROUP)
563: return;
564:
565: /* Ratelimit prunes or asserts */
566: if(uvifs[vifi].uv_flags & VIFF_POINT_TO_POINT) {
567:
568: mrtentry_ptr = find_route(source, group, MRTF_SG, DONT_CREATE);
569: if(mrtentry_ptr == (mrtentry_t *)NULL)
570: return;
571:
572: /* Wrong vif on P2P interface - rate-limit prunes */
573:
574: if(mrtentry_ptr->last_prune[vifi] == virtual_time)
575: /* Skip due to rate-limiting */
576: return;
577: mrtentry_ptr->last_prune[vifi] = virtual_time;
578:
579: if(uvifs[vifi].uv_rmt_addr)
580: send_pim_jp(mrtentry_ptr, PIM_ACTION_PRUNE, vifi,
581: uvifs[vifi].uv_rmt_addr,
582: max_prune_timeout(mrtentry_ptr));
583: else
584: log(LOG_WARNING, 0,
585: "Can't send wrongvif prune on p2p %s: no remote address",
586: uvifs[vifi].uv_lcl_addr);
587: } else {
588: u_int32 pref, metric;
589:
590: /* Wrong vif on LAN interface - rate-limit asserts */
591:
592: mrtentry_ptr = find_route(source, group, MRTF_SG, DONT_CREATE);
593: if(mrtentry_ptr == (mrtentry_t *)NULL) {
594: pref = 0x7fffffff;
595: metric = 0x7fffffff;
596: }
597: else {
598: pref = mrtentry_ptr->source->preference;
599: metric = mrtentry_ptr->source->metric;
600: }
601:
602: if(mrtentry_ptr->last_assert[vifi] == virtual_time)
603: /* Skip due to rate-limiting */
604: return;
605: mrtentry_ptr->last_assert[vifi] = virtual_time;
606:
607: /* Send the assert */
608: send_pim_assert(source, group, vifi, pref, metric);
609: }
610: }
611:
612:
613: void trigger_prune_alert(mrtentry_ptr)
614: mrtentry_t *mrtentry_ptr;
615: {
616: IF_DEBUG(DEBUG_MRT)
617: log(LOG_DEBUG, 0, "Now negative cache for src %s, grp %s - pruning",
618: inet_fmt(mrtentry_ptr->source->address, s1),
619: inet_fmt(mrtentry_ptr->group->group, s2));
620:
621: /* Set the entry timer to the max of the prune timers */
622: SET_TIMER(mrtentry_ptr->timer, max_prune_timeout(mrtentry_ptr));
623:
624: /* Send a prune */
625: if(mrtentry_ptr->upstream)
626: send_pim_jp(mrtentry_ptr, PIM_ACTION_PRUNE, mrtentry_ptr->incoming,
627: mrtentry_ptr->upstream->address,
628: max_prune_timeout(mrtentry_ptr));
629: }
630:
631: void trigger_join_alert(mrtentry_ptr)
632: mrtentry_t *mrtentry_ptr;
633: {
634: IF_DEBUG(DEBUG_MRT)
635: log(LOG_DEBUG, 0, "Now forwarding state for src %s, grp %s - grafting",
636: inet_fmt(mrtentry_ptr->source->address, s1),
637: inet_fmt(mrtentry_ptr->group->group, s2));
638:
639: /* Refresh the entry timer */
640: SET_TIMER(mrtentry_ptr->timer, PIM_DATA_TIMEOUT);
641:
642: /* Send graft */
643: send_pim_graft(mrtentry_ptr);
644: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>