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
;
138 /* SGRpt entry could have empty oil */
139 pim_channel_del_oif(ch
->upstream
->channel_oil
, ch
->interface
,
142 * Do we have any S,G's that are inheriting?
143 * Nuke from on high too.
145 if (ch
->upstream
->sources
) {
146 struct pim_upstream
*child
;
147 struct listnode
*up_node
;
149 for (ALL_LIST_ELEMENTS_RO(ch
->upstream
->sources
,
151 pim_channel_del_oif(child
->channel_oil
,
153 PIM_OIF_FLAG_PROTO_STAR
);
158 * When this channel is removed
159 * we need to find all our children
160 * and make sure our pointers are fixed
162 pim_ifchannel_remove_children(ch
);
165 list_delete(&ch
->sources
);
167 listnode_delete(ch
->upstream
->ifchannels
, ch
);
169 if (ch
->ifjoin_state
!= PIM_IFJOIN_NOINFO
) {
170 pim_upstream_update_join_desired(pim_ifp
->pim
, ch
->upstream
);
173 /* upstream is common across ifchannels, check if upstream's
174 ifchannel list is empty before deleting upstream_del
175 ref count will take care of it.
177 if (ch
->upstream
->ref_count
> 0)
178 pim_upstream_del(pim_ifp
->pim
, ch
->upstream
,
179 __PRETTY_FUNCTION__
);
182 zlog_warn("%s: Avoiding deletion of upstream with ref_count %d "
183 "from ifchannel(%s): %s", __PRETTY_FUNCTION__
,
184 ch
->upstream
->ref_count
, ch
->interface
->name
,
189 THREAD_OFF(ch
->t_ifjoin_expiry_timer
);
190 THREAD_OFF(ch
->t_ifjoin_prune_pending_timer
);
191 THREAD_OFF(ch
->t_ifassert_timer
);
194 listnode_delete(ch
->parent
->sources
, ch
);
198 RB_REMOVE(pim_ifchannel_rb
, &pim_ifp
->ifchannel_rb
, ch
);
200 if (PIM_DEBUG_PIM_TRACE
)
201 zlog_debug("%s: ifchannel entry %s is deleted ",
202 __PRETTY_FUNCTION__
, ch
->sg_str
);
204 XFREE(MTYPE_PIM_IFCHANNEL
, ch
);
207 void pim_ifchannel_delete_all(struct interface
*ifp
)
209 struct pim_interface
*pim_ifp
;
210 struct pim_ifchannel
*ch
;
216 while (!RB_EMPTY(pim_ifchannel_rb
, &pim_ifp
->ifchannel_rb
)) {
217 ch
= RB_ROOT(pim_ifchannel_rb
, &pim_ifp
->ifchannel_rb
);
219 pim_ifchannel_delete(ch
);
223 static void delete_on_noinfo(struct pim_ifchannel
*ch
)
225 if (ch
->local_ifmembership
== PIM_IFMEMBERSHIP_NOINFO
226 && ch
->ifjoin_state
== PIM_IFJOIN_NOINFO
227 && ch
->t_ifjoin_expiry_timer
== NULL
)
228 pim_ifchannel_delete(ch
);
231 void pim_ifchannel_ifjoin_switch(const char *caller
, struct pim_ifchannel
*ch
,
232 enum pim_ifjoin_state new_state
)
234 enum pim_ifjoin_state old_state
= ch
->ifjoin_state
;
235 struct pim_interface
*pim_ifp
= ch
->interface
->info
;
237 if (PIM_DEBUG_PIM_EVENTS
)
239 "PIM_IFCHANNEL(%s): %s is switching from %s to %s",
240 ch
->interface
->name
, ch
->sg_str
,
241 pim_ifchannel_ifjoin_name(ch
->ifjoin_state
, ch
->flags
),
242 pim_ifchannel_ifjoin_name(new_state
, 0));
245 if (old_state
== new_state
) {
246 if (PIM_DEBUG_PIM_EVENTS
) {
248 "%s calledby %s: non-transition on state %d (%s)",
249 __PRETTY_FUNCTION__
, caller
, new_state
,
250 pim_ifchannel_ifjoin_name(new_state
, 0));
255 ch
->ifjoin_state
= new_state
;
257 if (ch
->sg
.src
.s_addr
== INADDR_ANY
) {
258 struct pim_upstream
*up
= ch
->upstream
;
259 struct pim_upstream
*child
;
260 struct listnode
*up_node
;
263 if (ch
->ifjoin_state
== PIM_IFJOIN_NOINFO
) {
264 for (ALL_LIST_ELEMENTS_RO(up
->sources
, up_node
,
266 struct channel_oil
*c_oil
=
269 if (PIM_DEBUG_PIM_TRACE
)
271 "%s %s: Prune(S,G)=%s from %s",
279 if (!pim_upstream_evaluate_join_desired(
280 pim_ifp
->pim
, child
)) {
282 c_oil
, ch
->interface
,
283 PIM_OIF_FLAG_PROTO_STAR
);
284 pim_upstream_update_join_desired(
285 pim_ifp
->pim
, child
);
289 * If the S,G has no if channel and the
291 * has output here then the *,G was
292 * supplying the implied
293 * if channel. So remove it.
294 * I think this is dead code now. is it?
296 if (c_oil
->oil
.mfcc_ttls
297 [pim_ifp
->mroute_vif_index
])
299 c_oil
, ch
->interface
,
300 PIM_OIF_FLAG_PROTO_STAR
);
303 if (ch
->ifjoin_state
== PIM_IFJOIN_JOIN
) {
304 for (ALL_LIST_ELEMENTS_RO(up
->sources
, up_node
,
306 if (PIM_DEBUG_PIM_TRACE
)
308 "%s %s: Join(S,G)=%s from %s",
314 if (pim_upstream_evaluate_join_desired(
315 pim_ifp
->pim
, child
)) {
319 PIM_OIF_FLAG_PROTO_STAR
);
320 pim_upstream_update_join_desired(
321 pim_ifp
->pim
, child
);
327 /* Transition to/from NOINFO ? */
328 if ((old_state
== PIM_IFJOIN_NOINFO
)
329 || (new_state
== PIM_IFJOIN_NOINFO
)) {
331 if (PIM_DEBUG_PIM_EVENTS
) {
332 zlog_debug("PIM_IFCHANNEL_%s: (S,G)=%s on interface %s",
333 ((new_state
== PIM_IFJOIN_NOINFO
) ? "DOWN"
335 ch
->sg_str
, ch
->interface
->name
);
339 Record uptime of state transition to/from NOINFO
341 ch
->ifjoin_creation
= pim_time_monotonic_sec();
343 pim_upstream_update_join_desired(pim_ifp
->pim
, ch
->upstream
);
344 pim_ifchannel_update_could_assert(ch
);
345 pim_ifchannel_update_assert_tracking_desired(ch
);
349 const char *pim_ifchannel_ifjoin_name(enum pim_ifjoin_state ifjoin_state
,
352 switch (ifjoin_state
) {
353 case PIM_IFJOIN_NOINFO
:
354 if (PIM_IF_FLAG_TEST_S_G_RPT(flags
))
359 case PIM_IFJOIN_JOIN
:
362 case PIM_IFJOIN_PRUNE
:
363 if (PIM_IF_FLAG_TEST_S_G_RPT(flags
))
368 case PIM_IFJOIN_PRUNE_PENDING
:
369 if (PIM_IF_FLAG_TEST_S_G_RPT(flags
))
374 case PIM_IFJOIN_PRUNE_TMP
:
375 if (PIM_IF_FLAG_TEST_S_G_RPT(flags
))
380 case PIM_IFJOIN_PRUNE_PENDING_TMP
:
381 if (PIM_IF_FLAG_TEST_S_G_RPT(flags
))
388 return "ifjoin_bad_state";
391 const char *pim_ifchannel_ifassert_name(enum pim_ifassert_state ifassert_state
)
393 switch (ifassert_state
) {
394 case PIM_IFASSERT_NOINFO
:
396 case PIM_IFASSERT_I_AM_WINNER
:
398 case PIM_IFASSERT_I_AM_LOSER
:
402 return "ifassert_bad_state";
406 RFC 4601: 4.6.5. Assert State Macros
408 AssertWinner(S,G,I) defaults to NULL and AssertWinnerMetric(S,G,I)
409 defaults to Infinity when in the NoInfo state.
411 void reset_ifassert_state(struct pim_ifchannel
*ch
)
413 struct in_addr any
= {.s_addr
= INADDR_ANY
};
415 THREAD_OFF(ch
->t_ifassert_timer
);
417 pim_ifassert_winner_set(ch
, PIM_IFASSERT_NOINFO
, any
,
418 router
->infinite_assert_metric
);
421 struct pim_ifchannel
*pim_ifchannel_find(struct interface
*ifp
,
422 struct prefix_sg
*sg
)
424 struct pim_interface
*pim_ifp
;
425 struct pim_ifchannel
*ch
;
426 struct pim_ifchannel lookup
;
431 zlog_warn("%s: (S,G)=%s: multicast not enabled on interface %s",
432 __PRETTY_FUNCTION__
, pim_str_sg_dump(sg
), ifp
->name
);
437 lookup
.interface
= ifp
;
438 ch
= RB_FIND(pim_ifchannel_rb
, &pim_ifp
->ifchannel_rb
, &lookup
);
443 static void ifmembership_set(struct pim_ifchannel
*ch
,
444 enum pim_ifmembership membership
)
446 struct pim_interface
*pim_ifp
= ch
->interface
->info
;
448 if (ch
->local_ifmembership
== membership
)
451 if (PIM_DEBUG_PIM_EVENTS
) {
452 zlog_debug("%s: (S,G)=%s membership now is %s on interface %s",
453 __PRETTY_FUNCTION__
, ch
->sg_str
,
454 membership
== PIM_IFMEMBERSHIP_INCLUDE
? "INCLUDE"
456 ch
->interface
->name
);
459 ch
->local_ifmembership
= membership
;
461 pim_upstream_update_join_desired(pim_ifp
->pim
, ch
->upstream
);
462 pim_ifchannel_update_could_assert(ch
);
463 pim_ifchannel_update_assert_tracking_desired(ch
);
467 void pim_ifchannel_membership_clear(struct interface
*ifp
)
469 struct pim_interface
*pim_ifp
;
470 struct pim_ifchannel
*ch
;
475 RB_FOREACH (ch
, pim_ifchannel_rb
, &pim_ifp
->ifchannel_rb
)
476 ifmembership_set(ch
, PIM_IFMEMBERSHIP_NOINFO
);
479 void pim_ifchannel_delete_on_noinfo(struct interface
*ifp
)
481 struct pim_interface
*pim_ifp
;
482 struct pim_ifchannel
*ch
, *ch_tmp
;
487 RB_FOREACH_SAFE (ch
, pim_ifchannel_rb
, &pim_ifp
->ifchannel_rb
, ch_tmp
)
488 delete_on_noinfo(ch
);
492 * For a given Interface, if we are given a S,G
493 * Find the *,G (If we have it).
494 * If we are passed a *,G, find the *,* ifchannel
497 static struct pim_ifchannel
*pim_ifchannel_find_parent(struct pim_ifchannel
*ch
)
499 struct prefix_sg parent_sg
= ch
->sg
;
500 struct pim_ifchannel
*parent
= NULL
;
503 if ((parent_sg
.src
.s_addr
!= INADDR_ANY
)
504 && (parent_sg
.grp
.s_addr
!= INADDR_ANY
)) {
505 parent_sg
.src
.s_addr
= INADDR_ANY
;
506 parent
= pim_ifchannel_find(ch
->interface
, &parent_sg
);
509 listnode_add(parent
->sources
, ch
);
516 struct pim_ifchannel
*pim_ifchannel_add(struct interface
*ifp
,
517 struct prefix_sg
*sg
,
518 uint8_t source_flags
, int up_flags
)
520 struct pim_interface
*pim_ifp
;
521 struct pim_ifchannel
*ch
;
522 struct pim_upstream
*up
;
524 ch
= pim_ifchannel_find(ifp
, sg
);
530 ch
= XCALLOC(MTYPE_PIM_IFCHANNEL
, sizeof(*ch
));
533 if ((source_flags
& PIM_ENCODE_RPT_BIT
)
534 && !(source_flags
& PIM_ENCODE_WC_BIT
))
535 PIM_IF_FLAG_SET_S_G_RPT(ch
->flags
);
539 pim_str_sg_set(sg
, ch
->sg_str
);
540 ch
->parent
= pim_ifchannel_find_parent(ch
);
541 if (ch
->sg
.src
.s_addr
== INADDR_ANY
) {
542 ch
->sources
= list_new();
544 (int (*)(void *, void *))pim_ifchannel_compare
;
548 pim_ifchannel_find_new_children(ch
);
549 ch
->local_ifmembership
= PIM_IFMEMBERSHIP_NOINFO
;
551 ch
->ifjoin_state
= PIM_IFJOIN_NOINFO
;
552 ch
->t_ifjoin_expiry_timer
= NULL
;
553 ch
->t_ifjoin_prune_pending_timer
= NULL
;
554 ch
->ifjoin_creation
= 0;
556 RB_INSERT(pim_ifchannel_rb
, &pim_ifp
->ifchannel_rb
, ch
);
558 up
= pim_upstream_add(pim_ifp
->pim
, sg
, NULL
, up_flags
,
559 __PRETTY_FUNCTION__
, ch
);
563 listnode_add_sort(up
->ifchannels
, ch
);
565 ch
->ifassert_my_metric
= pim_macro_ch_my_assert_metric_eval(ch
);
566 ch
->ifassert_winner_metric
= pim_macro_ch_my_assert_metric_eval(ch
);
568 ch
->ifassert_winner
.s_addr
= 0;
571 ch
->t_ifassert_timer
= NULL
;
572 ch
->ifassert_state
= PIM_IFASSERT_NOINFO
;
573 reset_ifassert_state(ch
);
574 if (pim_macro_ch_could_assert_eval(ch
))
575 PIM_IF_FLAG_SET_COULD_ASSERT(ch
->flags
);
577 PIM_IF_FLAG_UNSET_COULD_ASSERT(ch
->flags
);
579 if (pim_macro_assert_tracking_desired_eval(ch
))
580 PIM_IF_FLAG_SET_ASSERT_TRACKING_DESIRED(ch
->flags
);
582 PIM_IF_FLAG_UNSET_ASSERT_TRACKING_DESIRED(ch
->flags
);
584 if (PIM_DEBUG_PIM_TRACE
)
585 zlog_debug("%s: ifchannel %s is created ", __PRETTY_FUNCTION__
,
591 static void ifjoin_to_noinfo(struct pim_ifchannel
*ch
, bool ch_del
)
593 pim_forward_stop(ch
, !ch_del
);
594 pim_ifchannel_ifjoin_switch(__PRETTY_FUNCTION__
, ch
, PIM_IFJOIN_NOINFO
);
596 delete_on_noinfo(ch
);
599 static int on_ifjoin_expiry_timer(struct thread
*t
)
601 struct pim_ifchannel
*ch
;
605 ifjoin_to_noinfo(ch
, true);
606 /* ch may have been deleted */
611 static int on_ifjoin_prune_pending_timer(struct thread
*t
)
613 struct pim_ifchannel
*ch
;
614 int send_prune_echo
; /* boolean */
615 struct interface
*ifp
;
616 struct pim_interface
*pim_ifp
;
622 "%s: IFCHANNEL%s %s Prune Pending Timer Popped",
623 __PRETTY_FUNCTION__
, pim_str_sg_dump(&ch
->sg
),
624 pim_ifchannel_ifjoin_name(ch
->ifjoin_state
, ch
->flags
));
626 if (ch
->ifjoin_state
== PIM_IFJOIN_PRUNE_PENDING
) {
629 if (!PIM_IF_FLAG_TEST_S_G_RPT(ch
->flags
)) {
630 /* Send PruneEcho(S,G) ? */
632 (listcount(pim_ifp
->pim_neighbor_list
) > 1);
634 if (send_prune_echo
) {
637 rpf
.source_nexthop
.interface
= ifp
;
638 rpf
.rpf_addr
.u
.prefix4
=
639 pim_ifp
->primary_address
;
640 pim_jp_agg_single_upstream_send(
641 &rpf
, ch
->upstream
, 0);
644 ifjoin_to_noinfo(ch
, true);
646 /* If SGRpt flag is set on ifchannel, Trigger SGRpt
647 * message on RP path upon prune timer expiry.
649 ch
->ifjoin_state
= PIM_IFJOIN_PRUNE
;
651 struct pim_upstream
*parent
=
652 ch
->upstream
->parent
;
654 pim_upstream_update_join_desired(pim_ifp
->pim
,
657 pim_jp_agg_single_upstream_send(&parent
->rpf
,
661 /* from here ch may have been deleted */
667 static void check_recv_upstream(int is_join
, struct interface
*recv_ifp
,
668 struct in_addr upstream
, struct prefix_sg
*sg
,
669 uint8_t source_flags
, int holdtime
)
671 struct pim_upstream
*up
;
672 struct pim_interface
*pim_ifp
= recv_ifp
->info
;
674 /* Upstream (S,G) in Joined state ? */
675 up
= pim_upstream_find(pim_ifp
->pim
, sg
);
678 if (up
->join_state
!= PIM_UPSTREAM_JOINED
)
681 /* Upstream (S,G) in Joined state */
683 if (pim_rpf_addr_is_inaddr_any(&up
->rpf
)) {
684 /* RPF'(S,G) not found */
685 zlog_warn("%s %s: RPF'%s not found", __FILE__
,
686 __PRETTY_FUNCTION__
, up
->sg_str
);
690 /* upstream directed to RPF'(S,G) ? */
691 if (upstream
.s_addr
!= up
->rpf
.rpf_addr
.u
.prefix4
.s_addr
) {
692 char up_str
[INET_ADDRSTRLEN
];
693 char rpf_str
[PREFIX_STRLEN
];
694 pim_inet4_dump("<up?>", upstream
, up_str
, sizeof(up_str
));
695 pim_addr_dump("<rpf?>", &up
->rpf
.rpf_addr
, rpf_str
,
698 "%s %s: (S,G)=%s upstream=%s not directed to RPF'(S,G)=%s on interface %s",
699 __FILE__
, __PRETTY_FUNCTION__
, up
->sg_str
, up_str
,
700 rpf_str
, recv_ifp
->name
);
703 /* upstream directed to RPF'(S,G) */
706 /* Join(S,G) to RPF'(S,G) */
707 pim_upstream_join_suppress(up
, up
->rpf
.rpf_addr
.u
.prefix4
,
712 /* Prune to RPF'(S,G) */
714 if (source_flags
& PIM_RPT_BIT_MASK
) {
715 if (source_flags
& PIM_WILDCARD_BIT_MASK
) {
716 /* Prune(*,G) to RPF'(S,G) */
717 pim_upstream_join_timer_decrease_to_t_override(
722 /* Prune(S,G,rpt) to RPF'(S,G) */
723 pim_upstream_join_timer_decrease_to_t_override("Prune(S,G,rpt)",
728 /* Prune(S,G) to RPF'(S,G) */
729 pim_upstream_join_timer_decrease_to_t_override("Prune(S,G)", up
);
732 static int nonlocal_upstream(int is_join
, struct interface
*recv_ifp
,
733 struct in_addr upstream
, struct prefix_sg
*sg
,
734 uint8_t source_flags
, uint16_t holdtime
)
736 struct pim_interface
*recv_pim_ifp
;
737 int is_local
; /* boolean */
739 recv_pim_ifp
= recv_ifp
->info
;
740 zassert(recv_pim_ifp
);
742 is_local
= (upstream
.s_addr
== recv_pim_ifp
->primary_address
.s_addr
);
747 if (PIM_DEBUG_PIM_TRACE_DETAIL
) {
748 char up_str
[INET_ADDRSTRLEN
];
749 pim_inet4_dump("<upstream?>", upstream
, up_str
, sizeof(up_str
));
750 zlog_warn("%s: recv %s (S,G)=%s to non-local upstream=%s on %s",
751 __PRETTY_FUNCTION__
, is_join
? "join" : "prune",
752 pim_str_sg_dump(sg
), up_str
, recv_ifp
->name
);
756 * Since recv upstream addr was not directed to our primary
757 * address, check if we should react to it in any way.
759 check_recv_upstream(is_join
, recv_ifp
, upstream
, sg
, source_flags
,
762 return 1; /* non-local */
765 void pim_ifchannel_join_add(struct interface
*ifp
, struct in_addr neigh_addr
,
766 struct in_addr upstream
, struct prefix_sg
*sg
,
767 uint8_t source_flags
, uint16_t holdtime
)
769 struct pim_interface
*pim_ifp
;
770 struct pim_ifchannel
*ch
;
772 if (nonlocal_upstream(1 /* join */, ifp
, upstream
, sg
, source_flags
,
777 ch
= pim_ifchannel_add(ifp
, sg
, source_flags
,
778 PIM_UPSTREAM_FLAG_MASK_SRC_PIM
);
783 RFC 4601: 4.6.1. (S,G) Assert Message State Machine
785 Transitions from "I am Assert Loser" State
787 Receive Join(S,G) on Interface I
789 We receive a Join(S,G) that has the Upstream Neighbor Address
790 field set to my primary IP address on interface I. The action is
791 to transition to NoInfo state, delete this (S,G) assert state
792 (Actions A5 below), and allow the normal PIM Join/Prune mechanisms
795 Notice: The nonlocal_upstream() test above ensures the upstream
796 address of the join message is our primary address.
798 if (ch
->ifassert_state
== PIM_IFASSERT_I_AM_LOSER
) {
799 char neigh_str
[INET_ADDRSTRLEN
];
800 pim_inet4_dump("<neigh?>", neigh_addr
, neigh_str
,
802 zlog_warn("%s: Assert Loser recv Join%s from %s on %s",
803 __PRETTY_FUNCTION__
, ch
->sg_str
, neigh_str
,
806 assert_action_a5(ch
);
812 switch (ch
->ifjoin_state
) {
813 case PIM_IFJOIN_NOINFO
:
814 pim_ifchannel_ifjoin_switch(__PRETTY_FUNCTION__
, ch
,
816 if (pim_macro_chisin_oiflist(ch
)) {
817 pim_upstream_inherited_olist(pim_ifp
->pim
,
819 pim_forward_start(ch
);
822 * If we are going to be a LHR, we need to note it
824 if (ch
->upstream
->parent
&& (ch
->upstream
->parent
->flags
825 & PIM_UPSTREAM_FLAG_MASK_SRC_IGMP
)
826 && !(ch
->upstream
->flags
827 & PIM_UPSTREAM_FLAG_MASK_SRC_LHR
)) {
828 pim_upstream_ref(ch
->upstream
,
829 PIM_UPSTREAM_FLAG_MASK_SRC_LHR
,
830 __PRETTY_FUNCTION__
);
831 pim_upstream_keep_alive_timer_start(
832 ch
->upstream
, pim_ifp
->pim
->keep_alive_time
);
835 case PIM_IFJOIN_JOIN
:
836 zassert(!ch
->t_ifjoin_prune_pending_timer
);
839 In the JOIN state ch->t_ifjoin_expiry_timer may be NULL due to
841 previously received join message with holdtime=0xFFFF.
843 if (ch
->t_ifjoin_expiry_timer
) {
844 unsigned long remain
= thread_timer_remain_second(
845 ch
->t_ifjoin_expiry_timer
);
846 if (remain
> holdtime
) {
848 RFC 4601: 4.5.3. Receiving (S,G) Join/Prune
851 Transitions from Join State
853 The (S,G) downstream state machine on
854 interface I remains in
855 Join state, and the Expiry Timer (ET) is
857 maximum of its current value and the HoldTime
859 triggering Join/Prune message.
861 Conclusion: Do not change the ET if the
863 higher than the received join holdtime.
868 THREAD_OFF(ch
->t_ifjoin_expiry_timer
);
870 case PIM_IFJOIN_PRUNE
:
871 if (source_flags
& PIM_ENCODE_RPT_BIT
)
872 pim_ifchannel_ifjoin_switch(__PRETTY_FUNCTION__
, ch
,
876 * We have received a S,G join and we are in
877 * S,G RPT Prune state. Which means we need
878 * to transition to Join state and setup
879 * state as appropriate.
881 pim_ifchannel_ifjoin_switch(__PRETTY_FUNCTION__
, ch
,
883 PIM_IF_FLAG_UNSET_S_G_RPT(ch
->flags
);
884 if (pim_upstream_evaluate_join_desired(pim_ifp
->pim
,
886 pim_channel_add_oif(ch
->upstream
->channel_oil
,
888 PIM_OIF_FLAG_PROTO_PIM
);
889 pim_upstream_update_join_desired(pim_ifp
->pim
,
894 case PIM_IFJOIN_PRUNE_PENDING
:
895 THREAD_OFF(ch
->t_ifjoin_prune_pending_timer
);
896 if (source_flags
& PIM_ENCODE_RPT_BIT
) {
897 THREAD_OFF(ch
->t_ifjoin_expiry_timer
);
898 pim_ifchannel_ifjoin_switch(__PRETTY_FUNCTION__
, ch
,
901 pim_ifchannel_ifjoin_switch(__PRETTY_FUNCTION__
, ch
,
904 case PIM_IFJOIN_PRUNE_TMP
:
906 case PIM_IFJOIN_PRUNE_PENDING_TMP
:
910 if (holdtime
!= 0xFFFF) {
911 thread_add_timer(router
->master
, on_ifjoin_expiry_timer
, ch
,
912 holdtime
, &ch
->t_ifjoin_expiry_timer
);
916 void pim_ifchannel_prune(struct interface
*ifp
, struct in_addr upstream
,
917 struct prefix_sg
*sg
, uint8_t source_flags
,
920 struct pim_ifchannel
*ch
;
921 struct pim_interface
*pim_ifp
;
922 int jp_override_interval_msec
;
924 if (nonlocal_upstream(0 /* prune */, ifp
, upstream
, sg
, source_flags
,
929 ch
= pim_ifchannel_find(ifp
, sg
);
930 if (!ch
&& !(source_flags
& PIM_ENCODE_RPT_BIT
)) {
933 "%s: Received prune with no relevant ifchannel %s%s state: %d",
934 __PRETTY_FUNCTION__
, ifp
->name
,
935 pim_str_sg_dump(sg
), source_flags
);
939 ch
= pim_ifchannel_add(ifp
, sg
, source_flags
,
940 PIM_UPSTREAM_FLAG_MASK_SRC_PIM
);
946 switch (ch
->ifjoin_state
) {
947 case PIM_IFJOIN_NOINFO
:
948 if (source_flags
& PIM_ENCODE_RPT_BIT
) {
949 if (!(source_flags
& PIM_ENCODE_WC_BIT
))
950 PIM_IF_FLAG_SET_S_G_RPT(ch
->flags
);
952 ch
->ifjoin_state
= PIM_IFJOIN_PRUNE_PENDING
;
953 if (listcount(pim_ifp
->pim_neighbor_list
) > 1)
954 jp_override_interval_msec
=
955 pim_if_jp_override_interval_msec(ifp
);
957 jp_override_interval_msec
=
958 0; /* schedule to expire immediately */
959 /* If we called ifjoin_prune() directly instead, care
961 be taken not to use "ch" afterwards since it would be
964 THREAD_OFF(ch
->t_ifjoin_prune_pending_timer
);
965 THREAD_OFF(ch
->t_ifjoin_expiry_timer
);
966 thread_add_timer_msec(
967 router
->master
, on_ifjoin_prune_pending_timer
,
968 ch
, jp_override_interval_msec
,
969 &ch
->t_ifjoin_prune_pending_timer
);
970 thread_add_timer(router
->master
, on_ifjoin_expiry_timer
,
972 &ch
->t_ifjoin_expiry_timer
);
973 pim_upstream_update_join_desired(pim_ifp
->pim
,
977 case PIM_IFJOIN_PRUNE_PENDING
:
980 case PIM_IFJOIN_JOIN
:
981 THREAD_OFF(ch
->t_ifjoin_expiry_timer
);
983 pim_ifchannel_ifjoin_switch(__PRETTY_FUNCTION__
, ch
,
984 PIM_IFJOIN_PRUNE_PENDING
);
986 if (listcount(pim_ifp
->pim_neighbor_list
) > 1)
987 jp_override_interval_msec
=
988 pim_if_jp_override_interval_msec(ifp
);
990 jp_override_interval_msec
=
991 0; /* schedule to expire immediately */
992 /* If we called ifjoin_prune() directly instead, care should
993 be taken not to use "ch" afterwards since it would be
995 THREAD_OFF(ch
->t_ifjoin_prune_pending_timer
);
996 thread_add_timer_msec(router
->master
,
997 on_ifjoin_prune_pending_timer
, ch
,
998 jp_override_interval_msec
,
999 &ch
->t_ifjoin_prune_pending_timer
);
1001 case PIM_IFJOIN_PRUNE
:
1002 if (source_flags
& PIM_ENCODE_RPT_BIT
) {
1003 THREAD_OFF(ch
->t_ifjoin_prune_pending_timer
);
1004 thread_add_timer(router
->master
, on_ifjoin_expiry_timer
,
1006 &ch
->t_ifjoin_expiry_timer
);
1009 case PIM_IFJOIN_PRUNE_TMP
:
1010 if (source_flags
& PIM_ENCODE_RPT_BIT
) {
1011 ch
->ifjoin_state
= PIM_IFJOIN_PRUNE
;
1012 THREAD_OFF(ch
->t_ifjoin_expiry_timer
);
1013 thread_add_timer(router
->master
, on_ifjoin_expiry_timer
,
1015 &ch
->t_ifjoin_expiry_timer
);
1018 case PIM_IFJOIN_PRUNE_PENDING_TMP
:
1019 if (source_flags
& PIM_ENCODE_RPT_BIT
) {
1020 ch
->ifjoin_state
= PIM_IFJOIN_PRUNE_PENDING
;
1021 THREAD_OFF(ch
->t_ifjoin_expiry_timer
);
1022 thread_add_timer(router
->master
, on_ifjoin_expiry_timer
,
1024 &ch
->t_ifjoin_expiry_timer
);
1030 int pim_ifchannel_local_membership_add(struct interface
*ifp
,
1031 struct prefix_sg
*sg
)
1033 struct pim_ifchannel
*ch
, *starch
;
1034 struct pim_interface
*pim_ifp
;
1035 struct pim_instance
*pim
;
1037 /* PIM enabled on interface? */
1038 pim_ifp
= ifp
->info
;
1040 if (PIM_DEBUG_EVENTS
)
1041 zlog_debug("%s:%s Expected pim interface setup for %s",
1042 __PRETTY_FUNCTION__
,
1043 pim_str_sg_dump(sg
), ifp
->name
);
1047 if (!PIM_IF_TEST_PIM(pim_ifp
->options
)) {
1048 if (PIM_DEBUG_EVENTS
)
1049 zlog_debug("%s:%s PIM is not configured on this interface %s",
1050 __PRETTY_FUNCTION__
,
1051 pim_str_sg_dump(sg
), ifp
->name
);
1057 /* skip (*,G) ch creation if G is of type SSM */
1058 if (sg
->src
.s_addr
== INADDR_ANY
) {
1059 if (pim_is_grp_ssm(pim
, sg
->grp
)) {
1060 if (PIM_DEBUG_PIM_EVENTS
)
1062 "%s: local membership (S,G)=%s ignored as group is SSM",
1063 __PRETTY_FUNCTION__
,
1064 pim_str_sg_dump(sg
));
1069 ch
= pim_ifchannel_add(ifp
, sg
, 0, PIM_UPSTREAM_FLAG_MASK_SRC_IGMP
);
1071 if (PIM_DEBUG_EVENTS
)
1072 zlog_debug("%s:%s Unable to add ifchannel",
1073 __PRETTY_FUNCTION__
,
1074 pim_str_sg_dump(sg
));
1078 ifmembership_set(ch
, PIM_IFMEMBERSHIP_INCLUDE
);
1080 if (sg
->src
.s_addr
== INADDR_ANY
) {
1081 struct pim_upstream
*up
= pim_upstream_find(pim
, sg
);
1082 struct pim_upstream
*child
;
1083 struct listnode
*up_node
;
1087 for (ALL_LIST_ELEMENTS_RO(up
->sources
, up_node
, child
)) {
1088 if (PIM_DEBUG_EVENTS
)
1089 zlog_debug("%s %s: IGMP (S,G)=%s(%s) from %s",
1090 __FILE__
, __PRETTY_FUNCTION__
,
1091 child
->sg_str
, ifp
->name
,
1094 ch
= pim_ifchannel_find(ifp
, &child
->sg
);
1095 if (pim_upstream_evaluate_join_desired_interface(
1096 child
, ch
, starch
)) {
1097 pim_channel_add_oif(child
->channel_oil
, ifp
,
1098 PIM_OIF_FLAG_PROTO_STAR
);
1099 pim_upstream_switch(pim
, child
,
1100 PIM_UPSTREAM_JOINED
);
1104 if (pim
->spt
.switchover
== PIM_SPT_INFINITY
) {
1105 if (pim
->spt
.plist
) {
1106 struct prefix_list
*plist
= prefix_list_lookup(
1107 AFI_IP
, pim
->spt
.plist
);
1110 g
.prefixlen
= IPV4_MAX_PREFIXLEN
;
1111 g
.u
.prefix4
= up
->sg
.grp
;
1113 if (prefix_list_apply(plist
, &g
)
1115 pim_channel_add_oif(
1116 up
->channel_oil
, pim
->regiface
,
1117 PIM_OIF_FLAG_PROTO_IGMP
);
1121 pim_channel_add_oif(up
->channel_oil
, pim
->regiface
,
1122 PIM_OIF_FLAG_PROTO_IGMP
);
1128 void pim_ifchannel_local_membership_del(struct interface
*ifp
,
1129 struct prefix_sg
*sg
)
1131 struct pim_ifchannel
*starch
, *ch
, *orig
;
1132 struct pim_interface
*pim_ifp
;
1134 /* PIM enabled on interface? */
1135 pim_ifp
= ifp
->info
;
1138 if (!PIM_IF_TEST_PIM(pim_ifp
->options
))
1141 orig
= ch
= pim_ifchannel_find(ifp
, sg
);
1144 ifmembership_set(ch
, PIM_IFMEMBERSHIP_NOINFO
);
1146 if (sg
->src
.s_addr
== INADDR_ANY
) {
1147 struct pim_upstream
*up
= pim_upstream_find(pim_ifp
->pim
, sg
);
1148 struct pim_upstream
*child
;
1149 struct listnode
*up_node
, *up_nnode
;
1153 for (ALL_LIST_ELEMENTS(up
->sources
, up_node
, up_nnode
, child
)) {
1154 struct channel_oil
*c_oil
= child
->channel_oil
;
1155 struct pim_ifchannel
*chchannel
=
1156 pim_ifchannel_find(ifp
, &child
->sg
);
1158 pim_ifp
= ifp
->info
;
1160 if (PIM_DEBUG_EVENTS
)
1161 zlog_debug("%s %s: Prune(S,G)=%s(%s) from %s",
1162 __FILE__
, __PRETTY_FUNCTION__
,
1163 up
->sg_str
, ifp
->name
,
1166 ch
= pim_ifchannel_find(ifp
, &child
->sg
);
1168 && !pim_upstream_evaluate_join_desired_interface(
1170 pim_channel_del_oif(c_oil
, ifp
,
1171 PIM_OIF_FLAG_PROTO_STAR
);
1174 * If the S,G has no if channel and the c_oil still
1175 * has output here then the *,G was supplying the
1177 * if channel. So remove it.
1179 if (!chchannel
&& c_oil
1180 && c_oil
->oil
.mfcc_ttls
[pim_ifp
->mroute_vif_index
])
1181 pim_channel_del_oif(c_oil
, ifp
,
1182 PIM_OIF_FLAG_PROTO_STAR
);
1184 /* Child node removal/ref count-- will happen as part of
1185 * parent' delete_no_info */
1188 delete_on_noinfo(orig
);
1191 void pim_ifchannel_update_could_assert(struct pim_ifchannel
*ch
)
1193 int old_couldassert
=
1194 PIM_FORCE_BOOLEAN(PIM_IF_FLAG_TEST_COULD_ASSERT(ch
->flags
));
1195 int new_couldassert
=
1196 PIM_FORCE_BOOLEAN(pim_macro_ch_could_assert_eval(ch
));
1198 if (new_couldassert
== old_couldassert
)
1201 if (PIM_DEBUG_PIM_EVENTS
) {
1202 char src_str
[INET_ADDRSTRLEN
];
1203 char grp_str
[INET_ADDRSTRLEN
];
1204 pim_inet4_dump("<src?>", ch
->sg
.src
, src_str
, sizeof(src_str
));
1205 pim_inet4_dump("<grp?>", ch
->sg
.grp
, grp_str
, sizeof(grp_str
));
1206 zlog_debug("%s: CouldAssert(%s,%s,%s) changed from %d to %d",
1207 __PRETTY_FUNCTION__
, src_str
, grp_str
,
1208 ch
->interface
->name
, old_couldassert
,
1212 if (new_couldassert
) {
1213 /* CouldAssert(S,G,I) switched from FALSE to TRUE */
1214 PIM_IF_FLAG_SET_COULD_ASSERT(ch
->flags
);
1216 /* CouldAssert(S,G,I) switched from TRUE to FALSE */
1217 PIM_IF_FLAG_UNSET_COULD_ASSERT(ch
->flags
);
1219 if (ch
->ifassert_state
== PIM_IFASSERT_I_AM_WINNER
) {
1220 assert_action_a4(ch
);
1224 pim_ifchannel_update_my_assert_metric(ch
);
1228 my_assert_metric may be affected by:
1231 pim_ifp->primary_address
1232 rpf->source_nexthop.mrib_metric_preference;
1233 rpf->source_nexthop.mrib_route_metric;
1235 void pim_ifchannel_update_my_assert_metric(struct pim_ifchannel
*ch
)
1237 struct pim_assert_metric my_metric_new
=
1238 pim_macro_ch_my_assert_metric_eval(ch
);
1240 if (pim_assert_metric_match(&my_metric_new
, &ch
->ifassert_my_metric
))
1243 if (PIM_DEBUG_PIM_EVENTS
) {
1244 char src_str
[INET_ADDRSTRLEN
];
1245 char grp_str
[INET_ADDRSTRLEN
];
1246 char old_addr_str
[INET_ADDRSTRLEN
];
1247 char new_addr_str
[INET_ADDRSTRLEN
];
1248 pim_inet4_dump("<src?>", ch
->sg
.src
, src_str
, sizeof(src_str
));
1249 pim_inet4_dump("<grp?>", ch
->sg
.grp
, grp_str
, sizeof(grp_str
));
1250 pim_inet4_dump("<old_addr?>", ch
->ifassert_my_metric
.ip_address
,
1251 old_addr_str
, sizeof(old_addr_str
));
1252 pim_inet4_dump("<new_addr?>", my_metric_new
.ip_address
,
1253 new_addr_str
, sizeof(new_addr_str
));
1255 "%s: my_assert_metric(%s,%s,%s) changed from %u,%u,%u,%s to %u,%u,%u,%s",
1256 __PRETTY_FUNCTION__
, src_str
, grp_str
,
1257 ch
->interface
->name
,
1258 ch
->ifassert_my_metric
.rpt_bit_flag
,
1259 ch
->ifassert_my_metric
.metric_preference
,
1260 ch
->ifassert_my_metric
.route_metric
, old_addr_str
,
1261 my_metric_new
.rpt_bit_flag
,
1262 my_metric_new
.metric_preference
,
1263 my_metric_new
.route_metric
, new_addr_str
);
1266 ch
->ifassert_my_metric
= my_metric_new
;
1268 if (pim_assert_metric_better(&ch
->ifassert_my_metric
,
1269 &ch
->ifassert_winner_metric
)) {
1270 assert_action_a5(ch
);
1274 void pim_ifchannel_update_assert_tracking_desired(struct pim_ifchannel
*ch
)
1276 int old_atd
= PIM_FORCE_BOOLEAN(
1277 PIM_IF_FLAG_TEST_ASSERT_TRACKING_DESIRED(ch
->flags
));
1279 PIM_FORCE_BOOLEAN(pim_macro_assert_tracking_desired_eval(ch
));
1281 if (new_atd
== old_atd
)
1284 if (PIM_DEBUG_PIM_EVENTS
) {
1285 char src_str
[INET_ADDRSTRLEN
];
1286 char grp_str
[INET_ADDRSTRLEN
];
1287 pim_inet4_dump("<src?>", ch
->sg
.src
, src_str
, sizeof(src_str
));
1288 pim_inet4_dump("<grp?>", ch
->sg
.grp
, grp_str
, sizeof(grp_str
));
1290 "%s: AssertTrackingDesired(%s,%s,%s) changed from %d to %d",
1291 __PRETTY_FUNCTION__
, src_str
, grp_str
,
1292 ch
->interface
->name
, old_atd
, new_atd
);
1296 /* AssertTrackingDesired(S,G,I) switched from FALSE to TRUE */
1297 PIM_IF_FLAG_SET_ASSERT_TRACKING_DESIRED(ch
->flags
);
1299 /* AssertTrackingDesired(S,G,I) switched from TRUE to FALSE */
1300 PIM_IF_FLAG_UNSET_ASSERT_TRACKING_DESIRED(ch
->flags
);
1302 if (ch
->ifassert_state
== PIM_IFASSERT_I_AM_LOSER
) {
1303 assert_action_a5(ch
);
1309 * If we have a new pim interface, check to
1310 * see if any of the pre-existing channels have
1311 * their upstream out that way and turn on forwarding
1312 * for that ifchannel then.
1314 void pim_ifchannel_scan_forward_start(struct interface
*new_ifp
)
1316 struct pim_interface
*new_pim_ifp
= new_ifp
->info
;
1317 struct pim_instance
*pim
= new_pim_ifp
->pim
;
1318 struct interface
*ifp
;
1320 FOR_ALL_INTERFACES (pim
->vrf
, ifp
) {
1321 struct pim_interface
*loop_pim_ifp
= ifp
->info
;
1322 struct pim_ifchannel
*ch
;
1327 if (new_pim_ifp
== loop_pim_ifp
)
1330 RB_FOREACH (ch
, pim_ifchannel_rb
, &loop_pim_ifp
->ifchannel_rb
) {
1331 if (ch
->ifjoin_state
== PIM_IFJOIN_JOIN
) {
1332 struct pim_upstream
*up
= ch
->upstream
;
1333 if ((!up
->channel_oil
)
1334 && (up
->rpf
.source_nexthop
1335 .interface
== new_ifp
))
1336 pim_forward_start(ch
);
1343 * Downstream per-interface (S,G,rpt) state machine
1344 * states that we need to move (S,G,rpt) items
1345 * into different states at the start of the
1346 * reception of a *,G join as well, when
1347 * we get End of Message
1349 void pim_ifchannel_set_star_g_join_state(struct pim_ifchannel
*ch
, int eom
,
1352 bool send_upstream_starg
= false;
1353 struct pim_ifchannel
*child
;
1354 struct listnode
*ch_node
, *nch_node
;
1355 struct pim_instance
*pim
=
1356 ((struct pim_interface
*)ch
->interface
->info
)->pim
;
1357 struct pim_upstream
*starup
= ch
->upstream
;
1359 if (PIM_DEBUG_PIM_TRACE
)
1361 "%s: %s %s eom: %d join %u", __PRETTY_FUNCTION__
,
1362 pim_ifchannel_ifjoin_name(ch
->ifjoin_state
, ch
->flags
),
1363 ch
->sg_str
, eom
, join
);
1367 for (ALL_LIST_ELEMENTS(ch
->sources
, ch_node
, nch_node
, child
)) {
1368 if (!PIM_IF_FLAG_TEST_S_G_RPT(child
->flags
))
1371 switch (child
->ifjoin_state
) {
1372 case PIM_IFJOIN_NOINFO
:
1373 case PIM_IFJOIN_JOIN
:
1375 case PIM_IFJOIN_PRUNE
:
1377 child
->ifjoin_state
= PIM_IFJOIN_PRUNE_TMP
;
1379 case PIM_IFJOIN_PRUNE_PENDING
:
1381 child
->ifjoin_state
=
1382 PIM_IFJOIN_PRUNE_PENDING_TMP
;
1384 case PIM_IFJOIN_PRUNE_TMP
:
1385 case PIM_IFJOIN_PRUNE_PENDING_TMP
:
1389 if (child
->ifjoin_state
== PIM_IFJOIN_PRUNE_PENDING_TMP
)
1390 THREAD_OFF(child
->t_ifjoin_prune_pending_timer
);
1391 THREAD_OFF(child
->t_ifjoin_expiry_timer
);
1393 PIM_IF_FLAG_UNSET_S_G_RPT(child
->flags
);
1394 child
->ifjoin_state
= PIM_IFJOIN_NOINFO
;
1396 if (I_am_RP(pim
, child
->sg
.grp
)) {
1397 pim_channel_add_oif(
1398 child
->upstream
->channel_oil
,
1399 ch
->interface
, PIM_OIF_FLAG_PROTO_STAR
);
1400 pim_upstream_switch(pim
, child
->upstream
,
1401 PIM_UPSTREAM_JOINED
);
1402 pim_jp_agg_single_upstream_send(
1403 &child
->upstream
->rpf
, child
->upstream
,
1406 send_upstream_starg
= true;
1408 delete_on_noinfo(child
);
1413 if (send_upstream_starg
)
1414 pim_jp_agg_single_upstream_send(&starup
->rpf
, starup
, true);
1417 unsigned int pim_ifchannel_hash_key(void *arg
)
1419 struct pim_ifchannel
*ch
= (struct pim_ifchannel
*)arg
;
1421 return jhash_2words(ch
->sg
.src
.s_addr
, ch
->sg
.grp
.s_addr
, 0);