3 * Copyright (C) 2008 Everton da Silva Marques
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; see the file COPYING; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
33 #include "pim_iface.h"
34 #include "pim_ifchannel.h"
35 #include "pim_zebra.h"
41 #include "pim_macro.h"
43 #include "pim_upstream.h"
47 RB_GENERATE(pim_ifchannel_rb
, pim_ifchannel
, pim_ifp_rb
, pim_ifchannel_compare
);
49 int pim_ifchannel_compare(const struct pim_ifchannel
*ch1
,
50 const struct pim_ifchannel
*ch2
)
52 struct pim_interface
*pim_ifp1
;
53 struct pim_interface
*pim_ifp2
;
55 pim_ifp1
= ch1
->interface
->info
;
56 pim_ifp2
= ch2
->interface
->info
;
58 if (pim_ifp1
->mroute_vif_index
< pim_ifp2
->mroute_vif_index
)
61 if (pim_ifp1
->mroute_vif_index
> pim_ifp2
->mroute_vif_index
)
64 if (ntohl(ch1
->sg
.grp
.s_addr
) < ntohl(ch2
->sg
.grp
.s_addr
))
67 if (ntohl(ch1
->sg
.grp
.s_addr
) > ntohl(ch2
->sg
.grp
.s_addr
))
70 if (ntohl(ch1
->sg
.src
.s_addr
) < ntohl(ch2
->sg
.src
.s_addr
))
73 if (ntohl(ch1
->sg
.src
.s_addr
) > ntohl(ch2
->sg
.src
.s_addr
))
80 * A (*,G) or a (*,*) is going away
81 * remove the parent pointer from
82 * those pointing at us
84 static void pim_ifchannel_remove_children(struct pim_ifchannel
*ch
)
86 struct pim_ifchannel
*child
;
91 while (!list_isempty(ch
->sources
)) {
92 child
= listnode_head(ch
->sources
);
94 listnode_delete(ch
->sources
, child
);
99 * A (*,G) or a (*,*) is being created
100 * find all the children that would point
103 static void pim_ifchannel_find_new_children(struct pim_ifchannel
*ch
)
105 struct pim_interface
*pim_ifp
= ch
->interface
->info
;
106 struct pim_ifchannel
*child
;
108 // Basic Sanity that we are not being silly
109 if ((ch
->sg
.src
.s_addr
!= INADDR_ANY
)
110 && (ch
->sg
.grp
.s_addr
!= INADDR_ANY
))
113 if ((ch
->sg
.src
.s_addr
== INADDR_ANY
)
114 && (ch
->sg
.grp
.s_addr
== INADDR_ANY
))
117 RB_FOREACH (child
, pim_ifchannel_rb
, &pim_ifp
->ifchannel_rb
) {
118 if ((ch
->sg
.grp
.s_addr
!= INADDR_ANY
)
119 && (child
->sg
.grp
.s_addr
== ch
->sg
.grp
.s_addr
)
122 listnode_add_sort(ch
->sources
, child
);
127 void pim_ifchannel_delete(struct pim_ifchannel
*ch
)
129 struct pim_interface
*pim_ifp
;
131 pim_ifp
= ch
->interface
->info
;
133 if (ch
->upstream
->channel_oil
) {
134 uint32_t mask
= PIM_OIF_FLAG_PROTO_PIM
;
135 if (ch
->upstream
->flags
& PIM_UPSTREAM_FLAG_MASK_SRC_IGMP
)
136 mask
|= PIM_OIF_FLAG_PROTO_IGMP
;
139 * A S,G RPT channel can have an empty oil, we also
140 * need to take into account the fact that a ifchannel
141 * might have been suppressing a *,G ifchannel from
142 * being inherited. So let's figure out what
143 * needs to be done here
145 if ((ch
->sg
.src
.s_addr
!= INADDR_ANY
) &&
146 pim_upstream_evaluate_join_desired_interface(
147 ch
->upstream
, ch
, ch
->parent
))
148 pim_channel_add_oif(ch
->upstream
->channel_oil
,
150 PIM_OIF_FLAG_PROTO_STAR
,
153 pim_channel_del_oif(ch
->upstream
->channel_oil
,
154 ch
->interface
, mask
, __func__
);
156 * Do we have any S,G's that are inheriting?
157 * Nuke from on high too.
159 if (ch
->upstream
->sources
) {
160 struct pim_upstream
*child
;
161 struct listnode
*up_node
;
163 for (ALL_LIST_ELEMENTS_RO(ch
->upstream
->sources
,
165 pim_channel_del_inherited_oif(
173 * When this channel is removed
174 * we need to find all our children
175 * and make sure our pointers are fixed
177 pim_ifchannel_remove_children(ch
);
180 list_delete(&ch
->sources
);
182 listnode_delete(ch
->upstream
->ifchannels
, ch
);
184 pim_upstream_update_join_desired(pim_ifp
->pim
, ch
->upstream
);
186 /* upstream is common across ifchannels, check if upstream's
187 ifchannel list is empty before deleting upstream_del
188 ref count will take care of it.
190 if (ch
->upstream
->ref_count
> 0)
191 pim_upstream_del(pim_ifp
->pim
, ch
->upstream
,
192 __PRETTY_FUNCTION__
);
195 if (PIM_DEBUG_PIM_TRACE
)
196 zlog_debug("%s: Avoiding deletion of upstream with ref_count %d "
197 "from ifchannel(%s): %s", __PRETTY_FUNCTION__
,
198 ch
->upstream
->ref_count
, ch
->interface
->name
,
204 THREAD_OFF(ch
->t_ifjoin_expiry_timer
);
205 THREAD_OFF(ch
->t_ifjoin_prune_pending_timer
);
206 THREAD_OFF(ch
->t_ifassert_timer
);
209 listnode_delete(ch
->parent
->sources
, ch
);
213 RB_REMOVE(pim_ifchannel_rb
, &pim_ifp
->ifchannel_rb
, ch
);
215 if (PIM_DEBUG_PIM_TRACE
)
216 zlog_debug("%s: ifchannel entry %s is deleted ",
217 __PRETTY_FUNCTION__
, ch
->sg_str
);
219 XFREE(MTYPE_PIM_IFCHANNEL
, ch
);
222 void pim_ifchannel_delete_all(struct interface
*ifp
)
224 struct pim_interface
*pim_ifp
;
225 struct pim_ifchannel
*ch
;
231 while (!RB_EMPTY(pim_ifchannel_rb
, &pim_ifp
->ifchannel_rb
)) {
232 ch
= RB_ROOT(pim_ifchannel_rb
, &pim_ifp
->ifchannel_rb
);
234 pim_ifchannel_ifjoin_switch(__PRETTY_FUNCTION__
,
235 ch
, PIM_IFJOIN_NOINFO
);
236 pim_ifchannel_delete(ch
);
240 static void delete_on_noinfo(struct pim_ifchannel
*ch
)
242 if (ch
->local_ifmembership
== PIM_IFMEMBERSHIP_NOINFO
243 && ch
->ifjoin_state
== PIM_IFJOIN_NOINFO
244 && ch
->t_ifjoin_expiry_timer
== NULL
)
245 pim_ifchannel_delete(ch
);
248 void pim_ifchannel_ifjoin_switch(const char *caller
, struct pim_ifchannel
*ch
,
249 enum pim_ifjoin_state new_state
)
251 enum pim_ifjoin_state old_state
= ch
->ifjoin_state
;
252 struct pim_interface
*pim_ifp
= ch
->interface
->info
;
253 struct pim_ifchannel
*child_ch
;
255 if (PIM_DEBUG_PIM_EVENTS
)
257 "PIM_IFCHANNEL(%s): %s is switching from %s to %s",
258 ch
->interface
->name
, ch
->sg_str
,
259 pim_ifchannel_ifjoin_name(ch
->ifjoin_state
, ch
->flags
),
260 pim_ifchannel_ifjoin_name(new_state
, 0));
263 if (old_state
== new_state
) {
264 if (PIM_DEBUG_PIM_EVENTS
) {
266 "%s calledby %s: non-transition on state %d (%s)",
267 __PRETTY_FUNCTION__
, caller
, new_state
,
268 pim_ifchannel_ifjoin_name(new_state
, 0));
273 ch
->ifjoin_state
= new_state
;
275 if (ch
->sg
.src
.s_addr
== INADDR_ANY
) {
276 struct pim_upstream
*up
= ch
->upstream
;
277 struct pim_upstream
*child
;
278 struct listnode
*up_node
;
281 if (ch
->ifjoin_state
== PIM_IFJOIN_NOINFO
) {
282 for (ALL_LIST_ELEMENTS_RO(up
->sources
, up_node
,
284 struct channel_oil
*c_oil
=
287 if (PIM_DEBUG_PIM_TRACE
)
289 "%s %s: Prune(S,G)=%s from %s",
298 * If the S,G has no if channel and the
300 * has output here then the *,G was
301 * supplying the implied
302 * if channel. So remove it.
304 if (c_oil
->oil
.mfcc_ttls
305 [pim_ifp
->mroute_vif_index
])
306 pim_channel_del_inherited_oif(
307 c_oil
, ch
->interface
,
311 if (ch
->ifjoin_state
== PIM_IFJOIN_JOIN
) {
312 for (ALL_LIST_ELEMENTS_RO(up
->sources
, up_node
,
314 if (PIM_DEBUG_PIM_TRACE
)
316 "%s %s: Join(S,G)=%s from %s",
322 /* check if the channel can be
323 * inherited into the SG's OIL
325 child_ch
= pim_ifchannel_find(
328 if (pim_upstream_eval_inherit_if(
329 child
, child_ch
, ch
)) {
333 PIM_OIF_FLAG_PROTO_STAR
,
335 pim_upstream_update_join_desired(
336 pim_ifp
->pim
, child
);
342 /* Transition to/from NOINFO ? */
343 if ((old_state
== PIM_IFJOIN_NOINFO
)
344 || (new_state
== PIM_IFJOIN_NOINFO
)) {
346 if (PIM_DEBUG_PIM_EVENTS
) {
347 zlog_debug("PIM_IFCHANNEL_%s: (S,G)=%s on interface %s",
348 ((new_state
== PIM_IFJOIN_NOINFO
) ? "DOWN"
350 ch
->sg_str
, ch
->interface
->name
);
354 Record uptime of state transition to/from NOINFO
356 ch
->ifjoin_creation
= pim_time_monotonic_sec();
358 pim_upstream_update_join_desired(pim_ifp
->pim
, ch
->upstream
);
359 pim_ifchannel_update_could_assert(ch
);
360 pim_ifchannel_update_assert_tracking_desired(ch
);
364 const char *pim_ifchannel_ifjoin_name(enum pim_ifjoin_state ifjoin_state
,
367 switch (ifjoin_state
) {
368 case PIM_IFJOIN_NOINFO
:
369 if (PIM_IF_FLAG_TEST_S_G_RPT(flags
))
374 case PIM_IFJOIN_JOIN
:
377 case PIM_IFJOIN_PRUNE
:
378 if (PIM_IF_FLAG_TEST_S_G_RPT(flags
))
383 case PIM_IFJOIN_PRUNE_PENDING
:
384 if (PIM_IF_FLAG_TEST_S_G_RPT(flags
))
389 case PIM_IFJOIN_PRUNE_TMP
:
390 if (PIM_IF_FLAG_TEST_S_G_RPT(flags
))
395 case PIM_IFJOIN_PRUNE_PENDING_TMP
:
396 if (PIM_IF_FLAG_TEST_S_G_RPT(flags
))
403 return "ifjoin_bad_state";
406 const char *pim_ifchannel_ifassert_name(enum pim_ifassert_state ifassert_state
)
408 switch (ifassert_state
) {
409 case PIM_IFASSERT_NOINFO
:
411 case PIM_IFASSERT_I_AM_WINNER
:
413 case PIM_IFASSERT_I_AM_LOSER
:
417 return "ifassert_bad_state";
421 RFC 4601: 4.6.5. Assert State Macros
423 AssertWinner(S,G,I) defaults to NULL and AssertWinnerMetric(S,G,I)
424 defaults to Infinity when in the NoInfo state.
426 void reset_ifassert_state(struct pim_ifchannel
*ch
)
428 struct in_addr any
= {.s_addr
= INADDR_ANY
};
430 THREAD_OFF(ch
->t_ifassert_timer
);
432 pim_ifassert_winner_set(ch
, PIM_IFASSERT_NOINFO
, any
,
433 router
->infinite_assert_metric
);
436 struct pim_ifchannel
*pim_ifchannel_find(struct interface
*ifp
,
437 struct prefix_sg
*sg
)
439 struct pim_interface
*pim_ifp
;
440 struct pim_ifchannel
*ch
;
441 struct pim_ifchannel lookup
;
446 zlog_warn("%s: (S,G)=%s: multicast not enabled on interface %s",
447 __PRETTY_FUNCTION__
, pim_str_sg_dump(sg
), ifp
->name
);
452 lookup
.interface
= ifp
;
453 ch
= RB_FIND(pim_ifchannel_rb
, &pim_ifp
->ifchannel_rb
, &lookup
);
458 static void ifmembership_set(struct pim_ifchannel
*ch
,
459 enum pim_ifmembership membership
)
461 struct pim_interface
*pim_ifp
= ch
->interface
->info
;
463 if (ch
->local_ifmembership
== membership
)
466 if (PIM_DEBUG_PIM_EVENTS
) {
467 zlog_debug("%s: (S,G)=%s membership now is %s on interface %s",
468 __PRETTY_FUNCTION__
, ch
->sg_str
,
469 membership
== PIM_IFMEMBERSHIP_INCLUDE
? "INCLUDE"
471 ch
->interface
->name
);
474 ch
->local_ifmembership
= membership
;
476 pim_upstream_update_join_desired(pim_ifp
->pim
, ch
->upstream
);
477 pim_ifchannel_update_could_assert(ch
);
478 pim_ifchannel_update_assert_tracking_desired(ch
);
482 void pim_ifchannel_membership_clear(struct interface
*ifp
)
484 struct pim_interface
*pim_ifp
;
485 struct pim_ifchannel
*ch
;
490 RB_FOREACH (ch
, pim_ifchannel_rb
, &pim_ifp
->ifchannel_rb
)
491 ifmembership_set(ch
, PIM_IFMEMBERSHIP_NOINFO
);
494 void pim_ifchannel_delete_on_noinfo(struct interface
*ifp
)
496 struct pim_interface
*pim_ifp
;
497 struct pim_ifchannel
*ch
, *ch_tmp
;
502 RB_FOREACH_SAFE (ch
, pim_ifchannel_rb
, &pim_ifp
->ifchannel_rb
, ch_tmp
)
503 delete_on_noinfo(ch
);
507 * For a given Interface, if we are given a S,G
508 * Find the *,G (If we have it).
509 * If we are passed a *,G, find the *,* ifchannel
512 static struct pim_ifchannel
*pim_ifchannel_find_parent(struct pim_ifchannel
*ch
)
514 struct prefix_sg parent_sg
= ch
->sg
;
515 struct pim_ifchannel
*parent
= NULL
;
518 if ((parent_sg
.src
.s_addr
!= INADDR_ANY
)
519 && (parent_sg
.grp
.s_addr
!= INADDR_ANY
)) {
520 parent_sg
.src
.s_addr
= INADDR_ANY
;
521 parent
= pim_ifchannel_find(ch
->interface
, &parent_sg
);
524 listnode_add(parent
->sources
, ch
);
531 struct pim_ifchannel
*pim_ifchannel_add(struct interface
*ifp
,
532 struct prefix_sg
*sg
,
533 uint8_t source_flags
, int up_flags
)
535 struct pim_interface
*pim_ifp
;
536 struct pim_ifchannel
*ch
;
537 struct pim_upstream
*up
;
539 ch
= pim_ifchannel_find(ifp
, sg
);
545 ch
= XCALLOC(MTYPE_PIM_IFCHANNEL
, sizeof(*ch
));
548 if ((source_flags
& PIM_ENCODE_RPT_BIT
)
549 && !(source_flags
& PIM_ENCODE_WC_BIT
))
550 PIM_IF_FLAG_SET_S_G_RPT(ch
->flags
);
554 pim_str_sg_set(sg
, ch
->sg_str
);
555 ch
->parent
= pim_ifchannel_find_parent(ch
);
556 if (ch
->sg
.src
.s_addr
== INADDR_ANY
) {
557 ch
->sources
= list_new();
559 (int (*)(void *, void *))pim_ifchannel_compare
;
563 pim_ifchannel_find_new_children(ch
);
564 ch
->local_ifmembership
= PIM_IFMEMBERSHIP_NOINFO
;
566 ch
->ifjoin_state
= PIM_IFJOIN_NOINFO
;
567 ch
->t_ifjoin_expiry_timer
= NULL
;
568 ch
->t_ifjoin_prune_pending_timer
= NULL
;
569 ch
->ifjoin_creation
= 0;
571 RB_INSERT(pim_ifchannel_rb
, &pim_ifp
->ifchannel_rb
, ch
);
573 up
= pim_upstream_add(pim_ifp
->pim
, sg
, NULL
, up_flags
,
574 __PRETTY_FUNCTION__
, ch
);
578 listnode_add_sort(up
->ifchannels
, ch
);
580 ch
->ifassert_my_metric
= pim_macro_ch_my_assert_metric_eval(ch
);
581 ch
->ifassert_winner_metric
= pim_macro_ch_my_assert_metric_eval(ch
);
583 ch
->ifassert_winner
.s_addr
= INADDR_ANY
;
586 ch
->t_ifassert_timer
= NULL
;
587 ch
->ifassert_state
= PIM_IFASSERT_NOINFO
;
588 reset_ifassert_state(ch
);
589 if (pim_macro_ch_could_assert_eval(ch
))
590 PIM_IF_FLAG_SET_COULD_ASSERT(ch
->flags
);
592 PIM_IF_FLAG_UNSET_COULD_ASSERT(ch
->flags
);
594 if (pim_macro_assert_tracking_desired_eval(ch
))
595 PIM_IF_FLAG_SET_ASSERT_TRACKING_DESIRED(ch
->flags
);
597 PIM_IF_FLAG_UNSET_ASSERT_TRACKING_DESIRED(ch
->flags
);
599 if (PIM_DEBUG_PIM_TRACE
)
600 zlog_debug("%s: ifchannel %s is created ", __PRETTY_FUNCTION__
,
606 static void ifjoin_to_noinfo(struct pim_ifchannel
*ch
, bool ch_del
)
608 pim_forward_stop(ch
, !ch_del
);
609 pim_ifchannel_ifjoin_switch(__PRETTY_FUNCTION__
, ch
, PIM_IFJOIN_NOINFO
);
611 delete_on_noinfo(ch
);
614 static int on_ifjoin_expiry_timer(struct thread
*t
)
616 struct pim_ifchannel
*ch
;
620 if (PIM_DEBUG_PIM_TRACE
)
621 zlog_debug("%s: ifchannel %s expiry timer", __PRETTY_FUNCTION__
,
624 ifjoin_to_noinfo(ch
, true);
625 /* ch may have been deleted */
630 static int on_ifjoin_prune_pending_timer(struct thread
*t
)
632 struct pim_ifchannel
*ch
;
633 int send_prune_echo
; /* boolean */
634 struct interface
*ifp
;
635 struct pim_interface
*pim_ifp
;
639 if (PIM_DEBUG_PIM_TRACE
)
641 "%s: IFCHANNEL%s %s Prune Pending Timer Popped",
642 __PRETTY_FUNCTION__
, pim_str_sg_dump(&ch
->sg
),
643 pim_ifchannel_ifjoin_name(ch
->ifjoin_state
, ch
->flags
));
645 if (ch
->ifjoin_state
== PIM_IFJOIN_PRUNE_PENDING
) {
648 if (!PIM_IF_FLAG_TEST_S_G_RPT(ch
->flags
)) {
649 /* Send PruneEcho(S,G) ? */
651 (listcount(pim_ifp
->pim_neighbor_list
) > 1);
653 if (send_prune_echo
) {
656 rpf
.source_nexthop
.interface
= ifp
;
657 rpf
.rpf_addr
.u
.prefix4
=
658 pim_ifp
->primary_address
;
659 pim_jp_agg_single_upstream_send(
660 &rpf
, ch
->upstream
, 0);
663 ifjoin_to_noinfo(ch
, true);
665 /* If SGRpt flag is set on ifchannel, Trigger SGRpt
666 * message on RP path upon prune timer expiry.
668 ch
->ifjoin_state
= PIM_IFJOIN_PRUNE
;
670 struct pim_upstream
*parent
=
671 ch
->upstream
->parent
;
673 pim_upstream_update_join_desired(pim_ifp
->pim
,
676 pim_jp_agg_single_upstream_send(&parent
->rpf
,
680 /* from here ch may have been deleted */
686 static void check_recv_upstream(int is_join
, struct interface
*recv_ifp
,
687 struct in_addr upstream
, struct prefix_sg
*sg
,
688 uint8_t source_flags
, int holdtime
)
690 struct pim_upstream
*up
;
691 struct pim_interface
*pim_ifp
= recv_ifp
->info
;
693 /* Upstream (S,G) in Joined state ? */
694 up
= pim_upstream_find(pim_ifp
->pim
, sg
);
697 if (up
->join_state
!= PIM_UPSTREAM_JOINED
)
700 /* Upstream (S,G) in Joined state */
702 if (pim_rpf_addr_is_inaddr_any(&up
->rpf
)) {
703 /* RPF'(S,G) not found */
704 zlog_warn("%s %s: RPF'%s not found", __FILE__
,
705 __PRETTY_FUNCTION__
, up
->sg_str
);
709 /* upstream directed to RPF'(S,G) ? */
710 if (upstream
.s_addr
!= up
->rpf
.rpf_addr
.u
.prefix4
.s_addr
) {
711 char up_str
[INET_ADDRSTRLEN
];
712 char rpf_str
[PREFIX_STRLEN
];
713 pim_inet4_dump("<up?>", upstream
, up_str
, sizeof(up_str
));
714 pim_addr_dump("<rpf?>", &up
->rpf
.rpf_addr
, rpf_str
,
717 "%s %s: (S,G)=%s upstream=%s not directed to RPF'(S,G)=%s on interface %s",
718 __FILE__
, __PRETTY_FUNCTION__
, up
->sg_str
, up_str
,
719 rpf_str
, recv_ifp
->name
);
722 /* upstream directed to RPF'(S,G) */
725 /* Join(S,G) to RPF'(S,G) */
726 pim_upstream_join_suppress(up
, up
->rpf
.rpf_addr
.u
.prefix4
,
731 /* Prune to RPF'(S,G) */
733 if (source_flags
& PIM_RPT_BIT_MASK
) {
734 if (source_flags
& PIM_WILDCARD_BIT_MASK
) {
735 /* Prune(*,G) to RPF'(S,G) */
736 pim_upstream_join_timer_decrease_to_t_override(
741 /* Prune(S,G,rpt) to RPF'(S,G) */
742 pim_upstream_join_timer_decrease_to_t_override("Prune(S,G,rpt)",
747 /* Prune(S,G) to RPF'(S,G) */
748 pim_upstream_join_timer_decrease_to_t_override("Prune(S,G)", up
);
751 static int nonlocal_upstream(int is_join
, struct interface
*recv_ifp
,
752 struct in_addr upstream
, struct prefix_sg
*sg
,
753 uint8_t source_flags
, uint16_t holdtime
)
755 struct pim_interface
*recv_pim_ifp
;
756 int is_local
; /* boolean */
758 recv_pim_ifp
= recv_ifp
->info
;
759 zassert(recv_pim_ifp
);
761 is_local
= (upstream
.s_addr
== recv_pim_ifp
->primary_address
.s_addr
);
766 if (PIM_DEBUG_PIM_TRACE_DETAIL
) {
767 char up_str
[INET_ADDRSTRLEN
];
768 pim_inet4_dump("<upstream?>", upstream
, up_str
, sizeof(up_str
));
769 zlog_warn("%s: recv %s (S,G)=%s to non-local upstream=%s on %s",
770 __PRETTY_FUNCTION__
, is_join
? "join" : "prune",
771 pim_str_sg_dump(sg
), up_str
, recv_ifp
->name
);
775 * Since recv upstream addr was not directed to our primary
776 * address, check if we should react to it in any way.
778 check_recv_upstream(is_join
, recv_ifp
, upstream
, sg
, source_flags
,
781 return 1; /* non-local */
784 static void pim_ifchannel_ifjoin_handler(struct pim_ifchannel
*ch
,
785 struct pim_interface
*pim_ifp
)
787 pim_ifchannel_ifjoin_switch(__PRETTY_FUNCTION__
, ch
,
789 PIM_IF_FLAG_UNSET_S_G_RPT(ch
->flags
);
790 /* check if the interface qualifies as an immediate
793 if (pim_upstream_evaluate_join_desired_interface(
796 pim_channel_add_oif(ch
->upstream
->channel_oil
,
798 PIM_OIF_FLAG_PROTO_PIM
,
800 pim_upstream_update_join_desired(pim_ifp
->pim
,
806 void pim_ifchannel_join_add(struct interface
*ifp
, struct in_addr neigh_addr
,
807 struct in_addr upstream
, struct prefix_sg
*sg
,
808 uint8_t source_flags
, uint16_t holdtime
)
810 struct pim_interface
*pim_ifp
;
811 struct pim_ifchannel
*ch
;
813 if (nonlocal_upstream(1 /* join */, ifp
, upstream
, sg
, source_flags
,
818 ch
= pim_ifchannel_add(ifp
, sg
, source_flags
,
819 PIM_UPSTREAM_FLAG_MASK_SRC_PIM
);
822 RFC 4601: 4.6.1. (S,G) Assert Message State Machine
824 Transitions from "I am Assert Loser" State
826 Receive Join(S,G) on Interface I
828 We receive a Join(S,G) that has the Upstream Neighbor Address
829 field set to my primary IP address on interface I. The action is
830 to transition to NoInfo state, delete this (S,G) assert state
831 (Actions A5 below), and allow the normal PIM Join/Prune mechanisms
834 Notice: The nonlocal_upstream() test above ensures the upstream
835 address of the join message is our primary address.
837 if (ch
->ifassert_state
== PIM_IFASSERT_I_AM_LOSER
) {
838 char neigh_str
[INET_ADDRSTRLEN
];
839 pim_inet4_dump("<neigh?>", neigh_addr
, neigh_str
,
841 zlog_warn("%s: Assert Loser recv Join%s from %s on %s",
842 __PRETTY_FUNCTION__
, ch
->sg_str
, neigh_str
,
845 assert_action_a5(ch
);
851 switch (ch
->ifjoin_state
) {
852 case PIM_IFJOIN_NOINFO
:
853 pim_ifchannel_ifjoin_switch(__PRETTY_FUNCTION__
, ch
,
855 if (pim_macro_chisin_oiflist(ch
)) {
856 pim_upstream_inherited_olist(pim_ifp
->pim
,
858 pim_forward_start(ch
);
861 * If we are going to be a LHR, we need to note it
863 if (ch
->upstream
->parent
&& (ch
->upstream
->parent
->flags
864 & PIM_UPSTREAM_FLAG_MASK_SRC_IGMP
)
865 && !(ch
->upstream
->flags
866 & PIM_UPSTREAM_FLAG_MASK_SRC_LHR
)) {
867 pim_upstream_ref(ch
->upstream
,
868 PIM_UPSTREAM_FLAG_MASK_SRC_LHR
,
869 __PRETTY_FUNCTION__
);
870 pim_upstream_keep_alive_timer_start(
871 ch
->upstream
, pim_ifp
->pim
->keep_alive_time
);
874 case PIM_IFJOIN_JOIN
:
875 zassert(!ch
->t_ifjoin_prune_pending_timer
);
878 In the JOIN state ch->t_ifjoin_expiry_timer may be NULL due to
880 previously received join message with holdtime=0xFFFF.
882 if (ch
->t_ifjoin_expiry_timer
) {
883 unsigned long remain
= thread_timer_remain_second(
884 ch
->t_ifjoin_expiry_timer
);
885 if (remain
> holdtime
) {
887 RFC 4601: 4.5.3. Receiving (S,G) Join/Prune
890 Transitions from Join State
892 The (S,G) downstream state machine on
893 interface I remains in
894 Join state, and the Expiry Timer (ET) is
896 maximum of its current value and the HoldTime
898 triggering Join/Prune message.
900 Conclusion: Do not change the ET if the
902 higher than the received join holdtime.
907 THREAD_OFF(ch
->t_ifjoin_expiry_timer
);
909 case PIM_IFJOIN_PRUNE
:
910 if (source_flags
& PIM_ENCODE_RPT_BIT
)
911 pim_ifchannel_ifjoin_switch(__PRETTY_FUNCTION__
, ch
,
914 pim_ifchannel_ifjoin_handler(ch
, pim_ifp
);
916 case PIM_IFJOIN_PRUNE_PENDING
:
917 THREAD_OFF(ch
->t_ifjoin_prune_pending_timer
);
918 if (source_flags
& PIM_ENCODE_RPT_BIT
) {
919 THREAD_OFF(ch
->t_ifjoin_expiry_timer
);
920 pim_ifchannel_ifjoin_switch(__PRETTY_FUNCTION__
, ch
,
923 pim_ifchannel_ifjoin_handler(ch
, pim_ifp
);
926 case PIM_IFJOIN_PRUNE_TMP
:
928 case PIM_IFJOIN_PRUNE_PENDING_TMP
:
932 if (holdtime
!= 0xFFFF) {
933 thread_add_timer(router
->master
, on_ifjoin_expiry_timer
, ch
,
934 holdtime
, &ch
->t_ifjoin_expiry_timer
);
938 void pim_ifchannel_prune(struct interface
*ifp
, struct in_addr upstream
,
939 struct prefix_sg
*sg
, uint8_t source_flags
,
942 struct pim_ifchannel
*ch
;
943 struct pim_interface
*pim_ifp
;
944 int jp_override_interval_msec
;
946 if (nonlocal_upstream(0 /* prune */, ifp
, upstream
, sg
, source_flags
,
951 ch
= pim_ifchannel_find(ifp
, sg
);
952 if (!ch
&& !(source_flags
& PIM_ENCODE_RPT_BIT
)) {
953 if (PIM_DEBUG_PIM_TRACE
)
955 "%s: Received prune with no relevant ifchannel %s%s state: %d",
956 __PRETTY_FUNCTION__
, ifp
->name
,
957 pim_str_sg_dump(sg
), source_flags
);
961 ch
= pim_ifchannel_add(ifp
, sg
, source_flags
,
962 PIM_UPSTREAM_FLAG_MASK_SRC_PIM
);
966 switch (ch
->ifjoin_state
) {
967 case PIM_IFJOIN_NOINFO
:
968 if (source_flags
& PIM_ENCODE_RPT_BIT
) {
969 if (!(source_flags
& PIM_ENCODE_WC_BIT
))
970 PIM_IF_FLAG_SET_S_G_RPT(ch
->flags
);
972 ch
->ifjoin_state
= PIM_IFJOIN_PRUNE_PENDING
;
973 if (listcount(pim_ifp
->pim_neighbor_list
) > 1)
974 jp_override_interval_msec
=
975 pim_if_jp_override_interval_msec(ifp
);
977 jp_override_interval_msec
=
978 0; /* schedule to expire immediately */
979 /* If we called ifjoin_prune() directly instead, care
981 be taken not to use "ch" afterwards since it would be
984 THREAD_OFF(ch
->t_ifjoin_prune_pending_timer
);
985 THREAD_OFF(ch
->t_ifjoin_expiry_timer
);
986 thread_add_timer_msec(
987 router
->master
, on_ifjoin_prune_pending_timer
,
988 ch
, jp_override_interval_msec
,
989 &ch
->t_ifjoin_prune_pending_timer
);
990 thread_add_timer(router
->master
, on_ifjoin_expiry_timer
,
992 &ch
->t_ifjoin_expiry_timer
);
993 pim_upstream_update_join_desired(pim_ifp
->pim
,
997 case PIM_IFJOIN_PRUNE_PENDING
:
1000 case PIM_IFJOIN_JOIN
:
1001 THREAD_OFF(ch
->t_ifjoin_expiry_timer
);
1003 pim_ifchannel_ifjoin_switch(__PRETTY_FUNCTION__
, ch
,
1004 PIM_IFJOIN_PRUNE_PENDING
);
1006 if (listcount(pim_ifp
->pim_neighbor_list
) > 1)
1007 jp_override_interval_msec
=
1008 pim_if_jp_override_interval_msec(ifp
);
1010 jp_override_interval_msec
=
1011 0; /* schedule to expire immediately */
1012 /* If we called ifjoin_prune() directly instead, care should
1013 be taken not to use "ch" afterwards since it would be
1015 THREAD_OFF(ch
->t_ifjoin_prune_pending_timer
);
1016 thread_add_timer_msec(router
->master
,
1017 on_ifjoin_prune_pending_timer
, ch
,
1018 jp_override_interval_msec
,
1019 &ch
->t_ifjoin_prune_pending_timer
);
1021 case PIM_IFJOIN_PRUNE
:
1022 if (source_flags
& PIM_ENCODE_RPT_BIT
) {
1023 THREAD_OFF(ch
->t_ifjoin_prune_pending_timer
);
1024 thread_add_timer(router
->master
, on_ifjoin_expiry_timer
,
1026 &ch
->t_ifjoin_expiry_timer
);
1029 case PIM_IFJOIN_PRUNE_TMP
:
1030 if (source_flags
& PIM_ENCODE_RPT_BIT
) {
1031 ch
->ifjoin_state
= PIM_IFJOIN_PRUNE
;
1032 THREAD_OFF(ch
->t_ifjoin_expiry_timer
);
1033 thread_add_timer(router
->master
, on_ifjoin_expiry_timer
,
1035 &ch
->t_ifjoin_expiry_timer
);
1038 case PIM_IFJOIN_PRUNE_PENDING_TMP
:
1039 if (source_flags
& PIM_ENCODE_RPT_BIT
) {
1040 ch
->ifjoin_state
= PIM_IFJOIN_PRUNE_PENDING
;
1041 THREAD_OFF(ch
->t_ifjoin_expiry_timer
);
1042 thread_add_timer(router
->master
, on_ifjoin_expiry_timer
,
1044 &ch
->t_ifjoin_expiry_timer
);
1050 int pim_ifchannel_local_membership_add(struct interface
*ifp
,
1051 struct prefix_sg
*sg
)
1053 struct pim_ifchannel
*ch
, *starch
;
1054 struct pim_interface
*pim_ifp
;
1055 struct pim_instance
*pim
;
1057 /* PIM enabled on interface? */
1058 pim_ifp
= ifp
->info
;
1060 if (PIM_DEBUG_EVENTS
)
1061 zlog_debug("%s:%s Expected pim interface setup for %s",
1062 __PRETTY_FUNCTION__
,
1063 pim_str_sg_dump(sg
), ifp
->name
);
1067 if (!PIM_IF_TEST_PIM(pim_ifp
->options
)) {
1068 if (PIM_DEBUG_EVENTS
)
1069 zlog_debug("%s:%s PIM is not configured on this interface %s",
1070 __PRETTY_FUNCTION__
,
1071 pim_str_sg_dump(sg
), ifp
->name
);
1077 /* skip (*,G) ch creation if G is of type SSM */
1078 if (sg
->src
.s_addr
== INADDR_ANY
) {
1079 if (pim_is_grp_ssm(pim
, sg
->grp
)) {
1080 if (PIM_DEBUG_PIM_EVENTS
)
1082 "%s: local membership (S,G)=%s ignored as group is SSM",
1083 __PRETTY_FUNCTION__
,
1084 pim_str_sg_dump(sg
));
1089 ch
= pim_ifchannel_add(ifp
, sg
, 0, PIM_UPSTREAM_FLAG_MASK_SRC_IGMP
);
1091 ifmembership_set(ch
, PIM_IFMEMBERSHIP_INCLUDE
);
1093 if (sg
->src
.s_addr
== INADDR_ANY
) {
1094 struct pim_upstream
*up
= pim_upstream_find(pim
, sg
);
1095 struct pim_upstream
*child
;
1096 struct listnode
*up_node
;
1100 for (ALL_LIST_ELEMENTS_RO(up
->sources
, up_node
, child
)) {
1101 if (PIM_DEBUG_EVENTS
)
1102 zlog_debug("%s %s: IGMP (S,G)=%s(%s) from %s",
1103 __FILE__
, __PRETTY_FUNCTION__
,
1104 child
->sg_str
, ifp
->name
,
1107 ch
= pim_ifchannel_find(ifp
, &child
->sg
);
1108 if (pim_upstream_evaluate_join_desired_interface(
1109 child
, ch
, starch
)) {
1110 pim_channel_add_oif(child
->channel_oil
, ifp
,
1111 PIM_OIF_FLAG_PROTO_STAR
,
1113 pim_upstream_update_join_desired(pim
, child
);
1117 if (pim
->spt
.switchover
== PIM_SPT_INFINITY
) {
1118 if (pim
->spt
.plist
) {
1119 struct prefix_list
*plist
= prefix_list_lookup(
1120 AFI_IP
, pim
->spt
.plist
);
1123 g
.prefixlen
= IPV4_MAX_PREFIXLEN
;
1124 g
.u
.prefix4
= up
->sg
.grp
;
1126 if (prefix_list_apply(plist
, &g
)
1128 pim_channel_add_oif(
1129 up
->channel_oil
, pim
->regiface
,
1130 PIM_OIF_FLAG_PROTO_IGMP
,
1135 pim_channel_add_oif(up
->channel_oil
, pim
->regiface
,
1136 PIM_OIF_FLAG_PROTO_IGMP
,
1143 void pim_ifchannel_local_membership_del(struct interface
*ifp
,
1144 struct prefix_sg
*sg
)
1146 struct pim_ifchannel
*starch
, *ch
, *orig
;
1147 struct pim_interface
*pim_ifp
;
1149 /* PIM enabled on interface? */
1150 pim_ifp
= ifp
->info
;
1153 if (!PIM_IF_TEST_PIM(pim_ifp
->options
))
1156 orig
= ch
= pim_ifchannel_find(ifp
, sg
);
1159 ifmembership_set(ch
, PIM_IFMEMBERSHIP_NOINFO
);
1161 if (sg
->src
.s_addr
== INADDR_ANY
) {
1162 struct pim_upstream
*up
= pim_upstream_find(pim_ifp
->pim
, sg
);
1163 struct pim_upstream
*child
;
1164 struct listnode
*up_node
, *up_nnode
;
1168 for (ALL_LIST_ELEMENTS(up
->sources
, up_node
, up_nnode
, child
)) {
1169 struct channel_oil
*c_oil
= child
->channel_oil
;
1170 struct pim_ifchannel
*chchannel
=
1171 pim_ifchannel_find(ifp
, &child
->sg
);
1173 pim_ifp
= ifp
->info
;
1175 if (PIM_DEBUG_EVENTS
)
1176 zlog_debug("%s %s: Prune(S,G)=%s(%s) from %s",
1177 __FILE__
, __PRETTY_FUNCTION__
,
1178 up
->sg_str
, ifp
->name
,
1181 ch
= pim_ifchannel_find(ifp
, &child
->sg
);
1183 * If the S,G has no if channel and the c_oil still
1184 * has output here then the *,G was supplying the
1186 * if channel. So remove it.
1188 if (!pim_upstream_evaluate_join_desired_interface(
1189 child
, ch
, starch
) ||
1191 c_oil
->oil
.mfcc_ttls
[pim_ifp
->mroute_vif_index
])) {
1192 pim_channel_del_inherited_oif(c_oil
, ifp
,
1196 /* Child node removal/ref count-- will happen as part of
1197 * parent' delete_no_info */
1200 delete_on_noinfo(orig
);
1203 void pim_ifchannel_update_could_assert(struct pim_ifchannel
*ch
)
1205 int old_couldassert
=
1206 PIM_FORCE_BOOLEAN(PIM_IF_FLAG_TEST_COULD_ASSERT(ch
->flags
));
1207 int new_couldassert
=
1208 PIM_FORCE_BOOLEAN(pim_macro_ch_could_assert_eval(ch
));
1210 if (new_couldassert
== old_couldassert
)
1213 if (PIM_DEBUG_PIM_EVENTS
) {
1214 char src_str
[INET_ADDRSTRLEN
];
1215 char grp_str
[INET_ADDRSTRLEN
];
1216 pim_inet4_dump("<src?>", ch
->sg
.src
, src_str
, sizeof(src_str
));
1217 pim_inet4_dump("<grp?>", ch
->sg
.grp
, grp_str
, sizeof(grp_str
));
1218 zlog_debug("%s: CouldAssert(%s,%s,%s) changed from %d to %d",
1219 __PRETTY_FUNCTION__
, src_str
, grp_str
,
1220 ch
->interface
->name
, old_couldassert
,
1224 if (new_couldassert
) {
1225 /* CouldAssert(S,G,I) switched from false to true */
1226 PIM_IF_FLAG_SET_COULD_ASSERT(ch
->flags
);
1228 /* CouldAssert(S,G,I) switched from true to false */
1229 PIM_IF_FLAG_UNSET_COULD_ASSERT(ch
->flags
);
1231 if (ch
->ifassert_state
== PIM_IFASSERT_I_AM_WINNER
) {
1232 assert_action_a4(ch
);
1236 pim_ifchannel_update_my_assert_metric(ch
);
1240 my_assert_metric may be affected by:
1243 pim_ifp->primary_address
1244 rpf->source_nexthop.mrib_metric_preference;
1245 rpf->source_nexthop.mrib_route_metric;
1247 void pim_ifchannel_update_my_assert_metric(struct pim_ifchannel
*ch
)
1249 struct pim_assert_metric my_metric_new
=
1250 pim_macro_ch_my_assert_metric_eval(ch
);
1252 if (pim_assert_metric_match(&my_metric_new
, &ch
->ifassert_my_metric
))
1255 if (PIM_DEBUG_PIM_EVENTS
) {
1256 char src_str
[INET_ADDRSTRLEN
];
1257 char grp_str
[INET_ADDRSTRLEN
];
1258 char old_addr_str
[INET_ADDRSTRLEN
];
1259 char new_addr_str
[INET_ADDRSTRLEN
];
1260 pim_inet4_dump("<src?>", ch
->sg
.src
, src_str
, sizeof(src_str
));
1261 pim_inet4_dump("<grp?>", ch
->sg
.grp
, grp_str
, sizeof(grp_str
));
1262 pim_inet4_dump("<old_addr?>", ch
->ifassert_my_metric
.ip_address
,
1263 old_addr_str
, sizeof(old_addr_str
));
1264 pim_inet4_dump("<new_addr?>", my_metric_new
.ip_address
,
1265 new_addr_str
, sizeof(new_addr_str
));
1267 "%s: my_assert_metric(%s,%s,%s) changed from %u,%u,%u,%s to %u,%u,%u,%s",
1268 __PRETTY_FUNCTION__
, src_str
, grp_str
,
1269 ch
->interface
->name
,
1270 ch
->ifassert_my_metric
.rpt_bit_flag
,
1271 ch
->ifassert_my_metric
.metric_preference
,
1272 ch
->ifassert_my_metric
.route_metric
, old_addr_str
,
1273 my_metric_new
.rpt_bit_flag
,
1274 my_metric_new
.metric_preference
,
1275 my_metric_new
.route_metric
, new_addr_str
);
1278 ch
->ifassert_my_metric
= my_metric_new
;
1280 if (pim_assert_metric_better(&ch
->ifassert_my_metric
,
1281 &ch
->ifassert_winner_metric
)) {
1282 assert_action_a5(ch
);
1286 void pim_ifchannel_update_assert_tracking_desired(struct pim_ifchannel
*ch
)
1288 int old_atd
= PIM_FORCE_BOOLEAN(
1289 PIM_IF_FLAG_TEST_ASSERT_TRACKING_DESIRED(ch
->flags
));
1291 PIM_FORCE_BOOLEAN(pim_macro_assert_tracking_desired_eval(ch
));
1293 if (new_atd
== old_atd
)
1296 if (PIM_DEBUG_PIM_EVENTS
) {
1297 char src_str
[INET_ADDRSTRLEN
];
1298 char grp_str
[INET_ADDRSTRLEN
];
1299 pim_inet4_dump("<src?>", ch
->sg
.src
, src_str
, sizeof(src_str
));
1300 pim_inet4_dump("<grp?>", ch
->sg
.grp
, grp_str
, sizeof(grp_str
));
1302 "%s: AssertTrackingDesired(%s,%s,%s) changed from %d to %d",
1303 __PRETTY_FUNCTION__
, src_str
, grp_str
,
1304 ch
->interface
->name
, old_atd
, new_atd
);
1308 /* AssertTrackingDesired(S,G,I) switched from false to true */
1309 PIM_IF_FLAG_SET_ASSERT_TRACKING_DESIRED(ch
->flags
);
1311 /* AssertTrackingDesired(S,G,I) switched from true to false */
1312 PIM_IF_FLAG_UNSET_ASSERT_TRACKING_DESIRED(ch
->flags
);
1314 if (ch
->ifassert_state
== PIM_IFASSERT_I_AM_LOSER
) {
1315 assert_action_a5(ch
);
1321 * If we have a new pim interface, check to
1322 * see if any of the pre-existing channels have
1323 * their upstream out that way and turn on forwarding
1324 * for that ifchannel then.
1326 void pim_ifchannel_scan_forward_start(struct interface
*new_ifp
)
1328 struct pim_interface
*new_pim_ifp
= new_ifp
->info
;
1329 struct pim_instance
*pim
= new_pim_ifp
->pim
;
1330 struct interface
*ifp
;
1332 FOR_ALL_INTERFACES (pim
->vrf
, ifp
) {
1333 struct pim_interface
*loop_pim_ifp
= ifp
->info
;
1334 struct pim_ifchannel
*ch
;
1339 if (new_pim_ifp
== loop_pim_ifp
)
1342 RB_FOREACH (ch
, pim_ifchannel_rb
, &loop_pim_ifp
->ifchannel_rb
) {
1343 if (ch
->ifjoin_state
== PIM_IFJOIN_JOIN
) {
1344 struct pim_upstream
*up
= ch
->upstream
;
1345 if ((!up
->channel_oil
)
1346 && (up
->rpf
.source_nexthop
1347 .interface
== new_ifp
))
1348 pim_forward_start(ch
);
1355 * Downstream per-interface (S,G,rpt) state machine
1356 * states that we need to move (S,G,rpt) items
1357 * into different states at the start of the
1358 * reception of a *,G join as well, when
1359 * we get End of Message
1361 void pim_ifchannel_set_star_g_join_state(struct pim_ifchannel
*ch
, int eom
,
1364 bool send_upstream_starg
= false;
1365 struct pim_ifchannel
*child
;
1366 struct listnode
*ch_node
, *nch_node
;
1367 struct pim_instance
*pim
=
1368 ((struct pim_interface
*)ch
->interface
->info
)->pim
;
1369 struct pim_upstream
*starup
= ch
->upstream
;
1371 if (PIM_DEBUG_PIM_TRACE
)
1373 "%s: %s %s eom: %d join %u", __PRETTY_FUNCTION__
,
1374 pim_ifchannel_ifjoin_name(ch
->ifjoin_state
, ch
->flags
),
1375 ch
->sg_str
, eom
, join
);
1379 for (ALL_LIST_ELEMENTS(ch
->sources
, ch_node
, nch_node
, child
)) {
1380 if (!PIM_IF_FLAG_TEST_S_G_RPT(child
->flags
))
1383 switch (child
->ifjoin_state
) {
1384 case PIM_IFJOIN_NOINFO
:
1385 case PIM_IFJOIN_JOIN
:
1387 case PIM_IFJOIN_PRUNE
:
1389 child
->ifjoin_state
= PIM_IFJOIN_PRUNE_TMP
;
1391 case PIM_IFJOIN_PRUNE_PENDING
:
1393 child
->ifjoin_state
=
1394 PIM_IFJOIN_PRUNE_PENDING_TMP
;
1396 case PIM_IFJOIN_PRUNE_TMP
:
1397 case PIM_IFJOIN_PRUNE_PENDING_TMP
:
1401 if (child
->ifjoin_state
== PIM_IFJOIN_PRUNE_PENDING_TMP
)
1402 THREAD_OFF(child
->t_ifjoin_prune_pending_timer
);
1403 THREAD_OFF(child
->t_ifjoin_expiry_timer
);
1405 PIM_IF_FLAG_UNSET_S_G_RPT(child
->flags
);
1406 child
->ifjoin_state
= PIM_IFJOIN_NOINFO
;
1408 if ((I_am_RP(pim
, child
->sg
.grp
)) &&
1409 (!pim_upstream_empty_inherited_olist(
1410 child
->upstream
))) {
1411 pim_channel_add_oif(
1412 child
->upstream
->channel_oil
,
1413 ch
->interface
, PIM_OIF_FLAG_PROTO_STAR
,
1415 pim_upstream_update_join_desired(pim
,
1418 send_upstream_starg
= true;
1420 delete_on_noinfo(child
);
1425 if (send_upstream_starg
)
1426 pim_jp_agg_single_upstream_send(&starup
->rpf
, starup
, true);
1429 unsigned int pim_ifchannel_hash_key(const void *arg
)
1431 const struct pim_ifchannel
*ch
= arg
;
1433 return jhash_2words(ch
->sg
.src
.s_addr
, ch
->sg
.grp
.s_addr
, 0);