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"
46 int pim_ifchannel_compare(struct pim_ifchannel
*ch1
, struct pim_ifchannel
*ch2
)
48 struct pim_interface
*pim_ifp1
;
49 struct pim_interface
*pim_ifp2
;
51 pim_ifp1
= ch1
->interface
->info
;
52 pim_ifp2
= ch2
->interface
->info
;
54 if (pim_ifp1
->mroute_vif_index
< pim_ifp2
->mroute_vif_index
)
57 if (pim_ifp1
->mroute_vif_index
> pim_ifp2
->mroute_vif_index
)
60 if (ntohl(ch1
->sg
.grp
.s_addr
) < ntohl(ch2
->sg
.grp
.s_addr
))
63 if (ntohl(ch1
->sg
.grp
.s_addr
) > ntohl(ch2
->sg
.grp
.s_addr
))
66 if (ntohl(ch1
->sg
.src
.s_addr
) < ntohl(ch2
->sg
.src
.s_addr
))
69 if (ntohl(ch1
->sg
.src
.s_addr
) > ntohl(ch2
->sg
.src
.s_addr
))
76 * A (*,G) or a (*,*) is going away
77 * remove the parent pointer from
78 * those pointing at us
80 static void pim_ifchannel_remove_children(struct pim_ifchannel
*ch
)
82 struct pim_ifchannel
*child
;
87 while (!list_isempty(ch
->sources
)) {
88 child
= listnode_head(ch
->sources
);
90 listnode_delete(ch
->sources
, child
);
95 * A (*,G) or a (*,*) is being created
96 * find all the children that would point
99 static void pim_ifchannel_find_new_children(struct pim_ifchannel
*ch
)
101 struct pim_interface
*pim_ifp
= ch
->interface
->info
;
102 struct pim_ifchannel
*child
;
103 struct listnode
*ch_node
;
105 // Basic Sanity that we are not being silly
106 if ((ch
->sg
.src
.s_addr
!= INADDR_ANY
)
107 && (ch
->sg
.grp
.s_addr
!= INADDR_ANY
))
110 if ((ch
->sg
.src
.s_addr
== INADDR_ANY
)
111 && (ch
->sg
.grp
.s_addr
== INADDR_ANY
))
114 for (ALL_LIST_ELEMENTS_RO(pim_ifp
->pim_ifchannel_list
, ch_node
,
116 if ((ch
->sg
.grp
.s_addr
!= INADDR_ANY
)
117 && (child
->sg
.grp
.s_addr
== ch
->sg
.grp
.s_addr
)
120 listnode_add_sort(ch
->sources
, child
);
125 void pim_ifchannel_free(struct pim_ifchannel
*ch
)
127 XFREE(MTYPE_PIM_IFCHANNEL
, ch
);
130 void pim_ifchannel_delete(struct pim_ifchannel
*ch
)
132 struct pim_interface
*pim_ifp
;
134 pim_ifp
= ch
->interface
->info
;
136 if (ch
->upstream
->channel_oil
) {
137 uint32_t mask
= PIM_OIF_FLAG_PROTO_PIM
;
138 if (ch
->upstream
->flags
& PIM_UPSTREAM_FLAG_MASK_SRC_IGMP
)
139 mask
= PIM_OIF_FLAG_PROTO_IGMP
;
141 /* SGRpt entry could have empty oil */
142 if (ch
->upstream
->channel_oil
)
143 pim_channel_del_oif(ch
->upstream
->channel_oil
,
144 ch
->interface
, mask
);
146 * Do we have any S,G's that are inheriting?
147 * Nuke from on high too.
149 if (ch
->upstream
->sources
) {
150 struct pim_upstream
*child
;
151 struct listnode
*up_node
;
153 for (ALL_LIST_ELEMENTS_RO(ch
->upstream
->sources
,
155 pim_channel_del_oif(child
->channel_oil
,
157 PIM_OIF_FLAG_PROTO_STAR
);
162 * When this channel is removed
163 * we need to find all our children
164 * and make sure our pointers are fixed
166 pim_ifchannel_remove_children(ch
);
169 list_delete(ch
->sources
);
171 listnode_delete(ch
->upstream
->ifchannels
, ch
);
173 if (ch
->ifjoin_state
!= PIM_IFJOIN_NOINFO
) {
174 pim_upstream_update_join_desired(pim_ifp
->pim
, ch
->upstream
);
177 /* upstream is common across ifchannels, check if upstream's
178 ifchannel list is empty before deleting upstream_del
179 ref count will take care of it.
181 pim_upstream_del(pim_ifp
->pim
, ch
->upstream
, __PRETTY_FUNCTION__
);
184 THREAD_OFF(ch
->t_ifjoin_expiry_timer
);
185 THREAD_OFF(ch
->t_ifjoin_prune_pending_timer
);
186 THREAD_OFF(ch
->t_ifassert_timer
);
189 listnode_delete(ch
->parent
->sources
, ch
);
193 notice that listnode_delete() can't be moved
194 into pim_ifchannel_free() because the later is
195 called by list_delete_all_node()
197 listnode_delete(pim_ifp
->pim_ifchannel_list
, ch
);
198 hash_release(pim_ifp
->pim_ifchannel_hash
, ch
);
199 listnode_delete(pim_ifp
->pim
->ifchannel_list
, ch
);
201 if (PIM_DEBUG_PIM_TRACE
)
202 zlog_debug("%s: ifchannel entry %s is deleted ",
203 __PRETTY_FUNCTION__
, ch
->sg_str
);
205 pim_ifchannel_free(ch
);
208 void pim_ifchannel_delete_all(struct interface
*ifp
)
210 struct pim_interface
*pim_ifp
;
211 struct listnode
*ifchannel_node
;
212 struct listnode
*ifchannel_nextnode
;
213 struct pim_ifchannel
*ifchannel
;
219 for (ALL_LIST_ELEMENTS(pim_ifp
->pim_ifchannel_list
, ifchannel_node
,
220 ifchannel_nextnode
, ifchannel
)) {
221 pim_ifchannel_delete(ifchannel
);
225 static void delete_on_noinfo(struct pim_ifchannel
*ch
)
227 if (ch
->local_ifmembership
== PIM_IFMEMBERSHIP_NOINFO
228 && ch
->ifjoin_state
== PIM_IFJOIN_NOINFO
229 && ch
->t_ifjoin_expiry_timer
== NULL
)
230 pim_ifchannel_delete(ch
);
233 void pim_ifchannel_ifjoin_switch(const char *caller
, struct pim_ifchannel
*ch
,
234 enum pim_ifjoin_state new_state
)
236 enum pim_ifjoin_state old_state
= ch
->ifjoin_state
;
237 struct pim_interface
*pim_ifp
= ch
->interface
->info
;
239 if (PIM_DEBUG_PIM_EVENTS
)
241 "PIM_IFCHANNEL(%s): %s is switching from %s to %s",
242 ch
->interface
->name
, ch
->sg_str
,
243 pim_ifchannel_ifjoin_name(ch
->ifjoin_state
, ch
->flags
),
244 pim_ifchannel_ifjoin_name(new_state
, 0));
247 if (old_state
== new_state
) {
248 if (PIM_DEBUG_PIM_EVENTS
) {
250 "%s calledby %s: non-transition on state %d (%s)",
251 __PRETTY_FUNCTION__
, caller
, new_state
,
252 pim_ifchannel_ifjoin_name(new_state
, 0));
257 ch
->ifjoin_state
= new_state
;
259 if (ch
->sg
.src
.s_addr
== INADDR_ANY
) {
260 struct pim_upstream
*up
= ch
->upstream
;
261 struct pim_upstream
*child
;
262 struct listnode
*up_node
;
265 if (ch
->ifjoin_state
== PIM_IFJOIN_NOINFO
) {
266 for (ALL_LIST_ELEMENTS_RO(up
->sources
, up_node
,
268 struct channel_oil
*c_oil
=
271 if (PIM_DEBUG_PIM_TRACE
)
273 "%s %s: Prune(S,G)=%s from %s",
281 if (!pim_upstream_evaluate_join_desired(
282 pim_ifp
->pim
, child
)) {
284 c_oil
, ch
->interface
,
285 PIM_OIF_FLAG_PROTO_STAR
);
286 pim_upstream_update_join_desired(
287 pim_ifp
->pim
, child
);
291 * If the S,G has no if channel and the
293 * has output here then the *,G was
294 * supplying the implied
295 * if channel. So remove it.
296 * I think this is dead code now. is it?
298 if (c_oil
->oil
.mfcc_ttls
299 [pim_ifp
->mroute_vif_index
])
301 c_oil
, ch
->interface
,
302 PIM_OIF_FLAG_PROTO_STAR
);
305 if (ch
->ifjoin_state
== PIM_IFJOIN_JOIN
) {
306 for (ALL_LIST_ELEMENTS_RO(up
->sources
, up_node
,
308 if (PIM_DEBUG_PIM_TRACE
)
310 "%s %s: Join(S,G)=%s from %s",
316 if (pim_upstream_evaluate_join_desired(
317 pim_ifp
->pim
, child
)) {
321 PIM_OIF_FLAG_PROTO_STAR
);
322 pim_upstream_update_join_desired(
323 pim_ifp
->pim
, child
);
329 /* Transition to/from NOINFO ? */
330 if ((old_state
== PIM_IFJOIN_NOINFO
)
331 || (new_state
== PIM_IFJOIN_NOINFO
)) {
333 if (PIM_DEBUG_PIM_EVENTS
) {
334 zlog_debug("PIM_IFCHANNEL_%s: (S,G)=%s on interface %s",
335 ((new_state
== PIM_IFJOIN_NOINFO
) ? "DOWN"
337 ch
->sg_str
, ch
->interface
->name
);
341 Record uptime of state transition to/from NOINFO
343 ch
->ifjoin_creation
= pim_time_monotonic_sec();
345 pim_upstream_update_join_desired(pim_ifp
->pim
, ch
->upstream
);
346 pim_ifchannel_update_could_assert(ch
);
347 pim_ifchannel_update_assert_tracking_desired(ch
);
351 const char *pim_ifchannel_ifjoin_name(enum pim_ifjoin_state ifjoin_state
,
354 switch (ifjoin_state
) {
355 case PIM_IFJOIN_NOINFO
:
356 if (PIM_IF_FLAG_TEST_S_G_RPT(flags
))
361 case PIM_IFJOIN_JOIN
:
364 case PIM_IFJOIN_PRUNE
:
367 case PIM_IFJOIN_PRUNE_PENDING
:
370 case PIM_IFJOIN_PRUNE_TMP
:
373 case PIM_IFJOIN_PRUNE_PENDING_TMP
:
378 return "ifjoin_bad_state";
381 const char *pim_ifchannel_ifassert_name(enum pim_ifassert_state ifassert_state
)
383 switch (ifassert_state
) {
384 case PIM_IFASSERT_NOINFO
:
386 case PIM_IFASSERT_I_AM_WINNER
:
388 case PIM_IFASSERT_I_AM_LOSER
:
392 return "ifassert_bad_state";
396 RFC 4601: 4.6.5. Assert State Macros
398 AssertWinner(S,G,I) defaults to NULL and AssertWinnerMetric(S,G,I)
399 defaults to Infinity when in the NoInfo state.
401 void reset_ifassert_state(struct pim_ifchannel
*ch
)
403 struct in_addr any
= {.s_addr
= INADDR_ANY
};
405 THREAD_OFF(ch
->t_ifassert_timer
);
407 pim_ifassert_winner_set(ch
, PIM_IFASSERT_NOINFO
, any
,
408 qpim_infinite_assert_metric
);
411 struct pim_ifchannel
*pim_ifchannel_find(struct interface
*ifp
,
412 struct prefix_sg
*sg
)
414 struct pim_interface
*pim_ifp
;
415 struct pim_ifchannel
*ch
;
416 struct pim_ifchannel lookup
;
421 zlog_warn("%s: (S,G)=%s: multicast not enabled on interface %s",
422 __PRETTY_FUNCTION__
, pim_str_sg_dump(sg
), ifp
->name
);
427 ch
= hash_lookup(pim_ifp
->pim_ifchannel_hash
, &lookup
);
432 static void ifmembership_set(struct pim_ifchannel
*ch
,
433 enum pim_ifmembership membership
)
435 struct pim_interface
*pim_ifp
= ch
->interface
->info
;
437 if (ch
->local_ifmembership
== membership
)
440 if (PIM_DEBUG_PIM_EVENTS
) {
441 zlog_debug("%s: (S,G)=%s membership now is %s on interface %s",
442 __PRETTY_FUNCTION__
, ch
->sg_str
,
443 membership
== PIM_IFMEMBERSHIP_INCLUDE
? "INCLUDE"
445 ch
->interface
->name
);
448 ch
->local_ifmembership
= membership
;
450 pim_upstream_update_join_desired(pim_ifp
->pim
, ch
->upstream
);
451 pim_ifchannel_update_could_assert(ch
);
452 pim_ifchannel_update_assert_tracking_desired(ch
);
456 void pim_ifchannel_membership_clear(struct interface
*ifp
)
458 struct pim_interface
*pim_ifp
;
459 struct listnode
*ch_node
;
460 struct pim_ifchannel
*ch
;
465 for (ALL_LIST_ELEMENTS_RO(pim_ifp
->pim_ifchannel_list
, ch_node
, ch
)) {
466 ifmembership_set(ch
, PIM_IFMEMBERSHIP_NOINFO
);
470 void pim_ifchannel_delete_on_noinfo(struct interface
*ifp
)
472 struct pim_interface
*pim_ifp
;
473 struct listnode
*node
;
474 struct listnode
*next_node
;
475 struct pim_ifchannel
*ch
;
480 for (ALL_LIST_ELEMENTS(pim_ifp
->pim_ifchannel_list
, node
, next_node
,
482 delete_on_noinfo(ch
);
487 * For a given Interface, if we are given a S,G
488 * Find the *,G (If we have it).
489 * If we are passed a *,G, find the *,* ifchannel
492 static struct pim_ifchannel
*pim_ifchannel_find_parent(struct pim_ifchannel
*ch
)
494 struct prefix_sg parent_sg
= ch
->sg
;
495 struct pim_ifchannel
*parent
= NULL
;
498 if ((parent_sg
.src
.s_addr
!= INADDR_ANY
)
499 && (parent_sg
.grp
.s_addr
!= INADDR_ANY
)) {
500 parent_sg
.src
.s_addr
= INADDR_ANY
;
501 parent
= pim_ifchannel_find(ch
->interface
, &parent_sg
);
504 listnode_add(parent
->sources
, ch
);
511 struct pim_ifchannel
*pim_ifchannel_add(struct interface
*ifp
,
512 struct prefix_sg
*sg
,
513 uint8_t source_flags
, int up_flags
)
515 struct pim_interface
*pim_ifp
;
516 struct pim_ifchannel
*ch
;
517 struct pim_upstream
*up
;
519 ch
= pim_ifchannel_find(ifp
, sg
);
525 ch
= XCALLOC(MTYPE_PIM_IFCHANNEL
, sizeof(*ch
));
528 "%s: pim_ifchannel_new() failure for (S,G)=%s on interface %s",
529 __PRETTY_FUNCTION__
, pim_str_sg_dump(sg
), ifp
->name
);
534 if ((source_flags
& PIM_ENCODE_RPT_BIT
)
535 && !(source_flags
& PIM_ENCODE_WC_BIT
))
536 PIM_IF_FLAG_SET_S_G_RPT(ch
->flags
);
540 pim_str_sg_set(sg
, ch
->sg_str
);
541 ch
->parent
= pim_ifchannel_find_parent(ch
);
542 if (ch
->sg
.src
.s_addr
== INADDR_ANY
) {
543 ch
->sources
= list_new();
545 (int (*)(void *, void *))pim_ifchannel_compare
;
549 pim_ifchannel_find_new_children(ch
);
550 ch
->local_ifmembership
= PIM_IFMEMBERSHIP_NOINFO
;
552 ch
->ifjoin_state
= PIM_IFJOIN_NOINFO
;
553 ch
->t_ifjoin_expiry_timer
= NULL
;
554 ch
->t_ifjoin_prune_pending_timer
= NULL
;
555 ch
->ifjoin_creation
= 0;
558 listnode_add_sort(pim_ifp
->pim_ifchannel_list
, ch
);
559 ch
= hash_get(pim_ifp
->pim_ifchannel_hash
, ch
, hash_alloc_intern
);
560 listnode_add_sort(pim_ifp
->pim
->ifchannel_list
, ch
);
562 up
= pim_upstream_add(pim_ifp
->pim
, sg
, NULL
, up_flags
,
563 __PRETTY_FUNCTION__
, ch
);
567 "%s: could not attach upstream (S,G)=%s on interface %s",
568 __PRETTY_FUNCTION__
, pim_str_sg_dump(sg
), ifp
->name
);
570 pim_ifchannel_remove_children(ch
);
572 list_delete(ch
->sources
);
574 THREAD_OFF(ch
->t_ifjoin_expiry_timer
);
575 THREAD_OFF(ch
->t_ifjoin_prune_pending_timer
);
576 THREAD_OFF(ch
->t_ifassert_timer
);
578 listnode_delete(pim_ifp
->pim_ifchannel_list
, ch
);
579 hash_release(pim_ifp
->pim_ifchannel_hash
, ch
);
580 listnode_delete(pim_ifp
->pim
->ifchannel_list
, ch
);
581 XFREE(MTYPE_PIM_IFCHANNEL
, ch
);
586 listnode_add_sort(up
->ifchannels
, ch
);
588 ch
->ifassert_my_metric
= pim_macro_ch_my_assert_metric_eval(ch
);
589 ch
->ifassert_winner_metric
= pim_macro_ch_my_assert_metric_eval(ch
);
591 ch
->ifassert_winner
.s_addr
= 0;
594 ch
->t_ifassert_timer
= NULL
;
595 ch
->ifassert_state
= PIM_IFASSERT_NOINFO
;
596 reset_ifassert_state(ch
);
597 if (pim_macro_ch_could_assert_eval(ch
))
598 PIM_IF_FLAG_SET_COULD_ASSERT(ch
->flags
);
600 PIM_IF_FLAG_UNSET_COULD_ASSERT(ch
->flags
);
602 if (pim_macro_assert_tracking_desired_eval(ch
))
603 PIM_IF_FLAG_SET_ASSERT_TRACKING_DESIRED(ch
->flags
);
605 PIM_IF_FLAG_UNSET_ASSERT_TRACKING_DESIRED(ch
->flags
);
607 if (PIM_DEBUG_PIM_TRACE
)
608 zlog_debug("%s: ifchannel %s is created ", __PRETTY_FUNCTION__
,
614 static void ifjoin_to_noinfo(struct pim_ifchannel
*ch
, bool ch_del
)
616 pim_forward_stop(ch
);
617 pim_ifchannel_ifjoin_switch(__PRETTY_FUNCTION__
, ch
, PIM_IFJOIN_NOINFO
);
619 delete_on_noinfo(ch
);
622 static int on_ifjoin_expiry_timer(struct thread
*t
)
624 struct pim_ifchannel
*ch
;
628 ifjoin_to_noinfo(ch
, true);
629 /* ch may have been deleted */
634 static int on_ifjoin_prune_pending_timer(struct thread
*t
)
636 struct pim_ifchannel
*ch
;
637 int send_prune_echo
; /* boolean */
638 struct interface
*ifp
;
639 struct pim_interface
*pim_ifp
;
643 if (ch
->ifjoin_state
== PIM_IFJOIN_PRUNE_PENDING
) {
644 /* Send PruneEcho(S,G) ? */
647 send_prune_echo
= (listcount(pim_ifp
->pim_neighbor_list
) > 1);
649 if (send_prune_echo
) {
652 rpf
.source_nexthop
.interface
= ifp
;
653 rpf
.rpf_addr
.u
.prefix4
= pim_ifp
->primary_address
;
654 pim_jp_agg_single_upstream_send(&rpf
, ch
->upstream
, 0);
656 /* If SGRpt flag is set on ifchannel, Trigger SGRpt
657 message on RP path upon prune timer expiry.
659 if (PIM_IF_FLAG_TEST_S_G_RPT(ch
->flags
)) {
661 pim_upstream_update_join_desired(pim_ifp
->pim
,
664 ch->ifjoin_state transition to NOINFO state
665 ch_del is set to 0 for not deleteing from here.
666 Holdtime expiry (ch_del set to 1) delete the entry.
668 ifjoin_to_noinfo(ch
, false);
670 ifjoin_to_noinfo(ch
, true);
671 /* from here ch may have been deleted */
674 "%s: IFCHANNEL%s Prune Pending Timer Popped while in %s state",
675 __PRETTY_FUNCTION__
, pim_str_sg_dump(&ch
->sg
),
676 pim_ifchannel_ifjoin_name(ch
->ifjoin_state
, ch
->flags
));
682 static void check_recv_upstream(int is_join
, struct interface
*recv_ifp
,
683 struct in_addr upstream
, struct prefix_sg
*sg
,
684 uint8_t source_flags
, int holdtime
)
686 struct pim_upstream
*up
;
687 struct pim_interface
*pim_ifp
= recv_ifp
->info
;
689 /* Upstream (S,G) in Joined state ? */
690 up
= pim_upstream_find(pim_ifp
->pim
, sg
);
693 if (up
->join_state
!= PIM_UPSTREAM_JOINED
)
696 /* Upstream (S,G) in Joined state */
698 if (pim_rpf_addr_is_inaddr_any(&up
->rpf
)) {
699 /* RPF'(S,G) not found */
700 zlog_warn("%s %s: RPF'%s not found", __FILE__
,
701 __PRETTY_FUNCTION__
, up
->sg_str
);
705 /* upstream directed to RPF'(S,G) ? */
706 if (upstream
.s_addr
!= up
->rpf
.rpf_addr
.u
.prefix4
.s_addr
) {
707 char up_str
[INET_ADDRSTRLEN
];
708 char rpf_str
[PREFIX_STRLEN
];
709 pim_inet4_dump("<up?>", upstream
, up_str
, sizeof(up_str
));
710 pim_addr_dump("<rpf?>", &up
->rpf
.rpf_addr
, rpf_str
,
713 "%s %s: (S,G)=%s upstream=%s not directed to RPF'(S,G)=%s on interface %s",
714 __FILE__
, __PRETTY_FUNCTION__
, up
->sg_str
, up_str
,
715 rpf_str
, recv_ifp
->name
);
718 /* upstream directed to RPF'(S,G) */
721 /* Join(S,G) to RPF'(S,G) */
722 pim_upstream_join_suppress(up
, up
->rpf
.rpf_addr
.u
.prefix4
,
727 /* Prune to RPF'(S,G) */
729 if (source_flags
& PIM_RPT_BIT_MASK
) {
730 if (source_flags
& PIM_WILDCARD_BIT_MASK
) {
731 /* Prune(*,G) to RPF'(S,G) */
732 pim_upstream_join_timer_decrease_to_t_override(
737 /* Prune(S,G,rpt) to RPF'(S,G) */
738 pim_upstream_join_timer_decrease_to_t_override("Prune(S,G,rpt)",
743 /* Prune(S,G) to RPF'(S,G) */
744 pim_upstream_join_timer_decrease_to_t_override("Prune(S,G)", up
);
747 static int nonlocal_upstream(int is_join
, struct interface
*recv_ifp
,
748 struct in_addr upstream
, struct prefix_sg
*sg
,
749 uint8_t source_flags
, uint16_t holdtime
)
751 struct pim_interface
*recv_pim_ifp
;
752 int is_local
; /* boolean */
754 recv_pim_ifp
= recv_ifp
->info
;
755 zassert(recv_pim_ifp
);
757 is_local
= (upstream
.s_addr
== recv_pim_ifp
->primary_address
.s_addr
);
762 if (PIM_DEBUG_PIM_TRACE_DETAIL
) {
763 char up_str
[INET_ADDRSTRLEN
];
764 pim_inet4_dump("<upstream?>", upstream
, up_str
, sizeof(up_str
));
765 zlog_warn("%s: recv %s (S,G)=%s to non-local upstream=%s on %s",
766 __PRETTY_FUNCTION__
, is_join
? "join" : "prune",
767 pim_str_sg_dump(sg
), up_str
, recv_ifp
->name
);
771 * Since recv upstream addr was not directed to our primary
772 * address, check if we should react to it in any way.
774 check_recv_upstream(is_join
, recv_ifp
, upstream
, sg
, source_flags
,
777 return 1; /* non-local */
780 void pim_ifchannel_join_add(struct interface
*ifp
, struct in_addr neigh_addr
,
781 struct in_addr upstream
, struct prefix_sg
*sg
,
782 uint8_t source_flags
, uint16_t holdtime
)
784 struct pim_interface
*pim_ifp
;
785 struct pim_ifchannel
*ch
;
787 if (nonlocal_upstream(1 /* join */, ifp
, upstream
, sg
, source_flags
,
792 ch
= pim_ifchannel_add(ifp
, sg
, source_flags
,
793 PIM_UPSTREAM_FLAG_MASK_SRC_PIM
);
798 RFC 4601: 4.6.1. (S,G) Assert Message State Machine
800 Transitions from "I am Assert Loser" State
802 Receive Join(S,G) on Interface I
804 We receive a Join(S,G) that has the Upstream Neighbor Address
805 field set to my primary IP address on interface I. The action is
806 to transition to NoInfo state, delete this (S,G) assert state
807 (Actions A5 below), and allow the normal PIM Join/Prune mechanisms
810 Notice: The nonlocal_upstream() test above ensures the upstream
811 address of the join message is our primary address.
813 if (ch
->ifassert_state
== PIM_IFASSERT_I_AM_LOSER
) {
814 char neigh_str
[INET_ADDRSTRLEN
];
815 pim_inet4_dump("<neigh?>", neigh_addr
, neigh_str
,
817 zlog_warn("%s: Assert Loser recv Join%s from %s on %s",
818 __PRETTY_FUNCTION__
, ch
->sg_str
, neigh_str
,
821 assert_action_a5(ch
);
827 switch (ch
->ifjoin_state
) {
828 case PIM_IFJOIN_NOINFO
:
829 pim_ifchannel_ifjoin_switch(__PRETTY_FUNCTION__
, ch
,
831 if (pim_macro_chisin_oiflist(ch
)) {
832 pim_upstream_inherited_olist(pim_ifp
->pim
,
834 pim_forward_start(ch
);
837 * If we are going to be a LHR, we need to note it
839 if (ch
->upstream
->parent
&& (ch
->upstream
->parent
->flags
840 & PIM_UPSTREAM_FLAG_MASK_SRC_IGMP
)
841 && !(ch
->upstream
->flags
842 & PIM_UPSTREAM_FLAG_MASK_SRC_LHR
)) {
843 pim_upstream_ref(ch
->upstream
,
844 PIM_UPSTREAM_FLAG_MASK_SRC_LHR
,
845 __PRETTY_FUNCTION__
);
846 pim_upstream_keep_alive_timer_start(
847 ch
->upstream
, qpim_keep_alive_time
);
850 case PIM_IFJOIN_JOIN
:
851 zassert(!ch
->t_ifjoin_prune_pending_timer
);
854 In the JOIN state ch->t_ifjoin_expiry_timer may be NULL due to
856 previously received join message with holdtime=0xFFFF.
858 if (ch
->t_ifjoin_expiry_timer
) {
859 unsigned long remain
= thread_timer_remain_second(
860 ch
->t_ifjoin_expiry_timer
);
861 if (remain
> holdtime
) {
863 RFC 4601: 4.5.3. Receiving (S,G) Join/Prune
866 Transitions from Join State
868 The (S,G) downstream state machine on
869 interface I remains in
870 Join state, and the Expiry Timer (ET) is
872 maximum of its current value and the HoldTime
874 triggering Join/Prune message.
876 Conclusion: Do not change the ET if the
878 higher than the received join holdtime.
883 THREAD_OFF(ch
->t_ifjoin_expiry_timer
);
885 case PIM_IFJOIN_PRUNE
:
886 if (source_flags
& PIM_ENCODE_RPT_BIT
)
887 pim_ifchannel_ifjoin_switch(__PRETTY_FUNCTION__
, ch
,
890 case PIM_IFJOIN_PRUNE_PENDING
:
891 THREAD_OFF(ch
->t_ifjoin_prune_pending_timer
);
892 if (source_flags
& PIM_ENCODE_RPT_BIT
) {
893 THREAD_OFF(ch
->t_ifjoin_expiry_timer
);
894 pim_ifchannel_ifjoin_switch(__PRETTY_FUNCTION__
, ch
,
897 pim_ifchannel_ifjoin_switch(__PRETTY_FUNCTION__
, ch
,
900 case PIM_IFJOIN_PRUNE_TMP
:
902 case PIM_IFJOIN_PRUNE_PENDING_TMP
:
906 if (holdtime
!= 0xFFFF) {
907 thread_add_timer(master
, on_ifjoin_expiry_timer
, ch
, holdtime
,
908 &ch
->t_ifjoin_expiry_timer
);
912 void pim_ifchannel_prune(struct interface
*ifp
, struct in_addr upstream
,
913 struct prefix_sg
*sg
, uint8_t source_flags
,
916 struct pim_ifchannel
*ch
;
917 struct pim_interface
*pim_ifp
;
918 int jp_override_interval_msec
;
920 if (nonlocal_upstream(0 /* prune */, ifp
, upstream
, sg
, source_flags
,
925 ch
= pim_ifchannel_find(ifp
, sg
);
926 if (!ch
&& !(source_flags
& PIM_ENCODE_RPT_BIT
)) {
929 "%s: Received prune with no relevant ifchannel %s(%s) state: %d",
930 __PRETTY_FUNCTION__
, ifp
->name
,
931 pim_str_sg_dump(sg
), source_flags
);
935 ch
= pim_ifchannel_add(ifp
, sg
, source_flags
,
936 PIM_UPSTREAM_FLAG_MASK_SRC_PIM
);
942 switch (ch
->ifjoin_state
) {
943 case PIM_IFJOIN_NOINFO
:
944 if (source_flags
& PIM_ENCODE_RPT_BIT
) {
945 if (!(source_flags
& PIM_ENCODE_WC_BIT
))
946 PIM_IF_FLAG_SET_S_G_RPT(ch
->flags
);
948 ch
->ifjoin_state
= PIM_IFJOIN_PRUNE_PENDING
;
949 if (listcount(pim_ifp
->pim_neighbor_list
) > 1)
950 jp_override_interval_msec
=
951 pim_if_jp_override_interval_msec(ifp
);
953 jp_override_interval_msec
=
954 0; /* schedule to expire immediately */
955 /* If we called ifjoin_prune() directly instead, care
957 be taken not to use "ch" afterwards since it would be
960 THREAD_OFF(ch
->t_ifjoin_prune_pending_timer
);
961 THREAD_OFF(ch
->t_ifjoin_expiry_timer
);
962 thread_add_timer_msec(
963 master
, on_ifjoin_prune_pending_timer
, ch
,
964 jp_override_interval_msec
,
965 &ch
->t_ifjoin_prune_pending_timer
);
966 thread_add_timer(master
, on_ifjoin_expiry_timer
, ch
,
967 holdtime
, &ch
->t_ifjoin_expiry_timer
);
968 pim_upstream_update_join_desired(pim_ifp
->pim
,
972 case PIM_IFJOIN_PRUNE_PENDING
:
975 case PIM_IFJOIN_JOIN
:
976 THREAD_OFF(ch
->t_ifjoin_expiry_timer
);
978 pim_ifchannel_ifjoin_switch(__PRETTY_FUNCTION__
, ch
,
979 PIM_IFJOIN_PRUNE_PENDING
);
981 if (listcount(pim_ifp
->pim_neighbor_list
) > 1)
982 jp_override_interval_msec
=
983 pim_if_jp_override_interval_msec(ifp
);
985 jp_override_interval_msec
=
986 0; /* schedule to expire immediately */
987 /* If we called ifjoin_prune() directly instead, care should
988 be taken not to use "ch" afterwards since it would be
990 THREAD_OFF(ch
->t_ifjoin_prune_pending_timer
);
991 thread_add_timer_msec(master
, on_ifjoin_prune_pending_timer
, ch
,
992 jp_override_interval_msec
,
993 &ch
->t_ifjoin_prune_pending_timer
);
995 case PIM_IFJOIN_PRUNE
:
996 if (source_flags
& PIM_ENCODE_RPT_BIT
) {
997 THREAD_OFF(ch
->t_ifjoin_prune_pending_timer
);
998 thread_add_timer(master
, on_ifjoin_expiry_timer
, ch
,
999 holdtime
, &ch
->t_ifjoin_expiry_timer
);
1002 case PIM_IFJOIN_PRUNE_TMP
:
1003 if (source_flags
& PIM_ENCODE_RPT_BIT
) {
1004 ch
->ifjoin_state
= PIM_IFJOIN_PRUNE
;
1005 THREAD_OFF(ch
->t_ifjoin_expiry_timer
);
1006 thread_add_timer(master
, on_ifjoin_expiry_timer
, ch
,
1007 holdtime
, &ch
->t_ifjoin_expiry_timer
);
1010 case PIM_IFJOIN_PRUNE_PENDING_TMP
:
1011 if (source_flags
& PIM_ENCODE_RPT_BIT
) {
1012 ch
->ifjoin_state
= PIM_IFJOIN_PRUNE_PENDING
;
1013 THREAD_OFF(ch
->t_ifjoin_expiry_timer
);
1014 thread_add_timer(master
, on_ifjoin_expiry_timer
, ch
,
1015 holdtime
, &ch
->t_ifjoin_expiry_timer
);
1021 int pim_ifchannel_local_membership_add(struct interface
*ifp
,
1022 struct prefix_sg
*sg
)
1024 struct pim_ifchannel
*ch
, *starch
;
1025 struct pim_interface
*pim_ifp
;
1026 struct pim_instance
*pim
;
1028 /* PIM enabled on interface? */
1029 pim_ifp
= ifp
->info
;
1032 if (!PIM_IF_TEST_PIM(pim_ifp
->options
))
1037 /* skip (*,G) ch creation if G is of type SSM */
1038 if (sg
->src
.s_addr
== INADDR_ANY
) {
1039 if (pim_is_grp_ssm(pim
, sg
->grp
)) {
1040 if (PIM_DEBUG_PIM_EVENTS
)
1042 "%s: local membership (S,G)=%s ignored as group is SSM",
1043 __PRETTY_FUNCTION__
,
1044 pim_str_sg_dump(sg
));
1049 ch
= pim_ifchannel_add(ifp
, sg
, 0, PIM_UPSTREAM_FLAG_MASK_SRC_IGMP
);
1054 ifmembership_set(ch
, PIM_IFMEMBERSHIP_INCLUDE
);
1056 if (sg
->src
.s_addr
== INADDR_ANY
) {
1057 struct pim_upstream
*up
= pim_upstream_find(pim
, sg
);
1058 struct pim_upstream
*child
;
1059 struct listnode
*up_node
;
1063 for (ALL_LIST_ELEMENTS_RO(up
->sources
, up_node
, child
)) {
1064 if (PIM_DEBUG_EVENTS
)
1065 zlog_debug("%s %s: IGMP (S,G)=%s(%s) from %s",
1066 __FILE__
, __PRETTY_FUNCTION__
,
1067 child
->sg_str
, ifp
->name
,
1070 ch
= pim_ifchannel_find(ifp
, &child
->sg
);
1071 if (pim_upstream_evaluate_join_desired_interface(
1072 child
, ch
, starch
)) {
1073 pim_channel_add_oif(child
->channel_oil
, ifp
,
1074 PIM_OIF_FLAG_PROTO_STAR
);
1075 pim_upstream_switch(pim
, child
,
1076 PIM_UPSTREAM_JOINED
);
1080 if (pim
->spt
.switchover
== PIM_SPT_INFINITY
) {
1081 if (pim
->spt
.plist
) {
1082 struct prefix_list
*plist
= prefix_list_lookup(
1083 AFI_IP
, pim
->spt
.plist
);
1086 g
.prefixlen
= IPV4_MAX_PREFIXLEN
;
1087 g
.u
.prefix4
= up
->sg
.grp
;
1089 if (prefix_list_apply(plist
, &g
)
1091 pim_channel_add_oif(
1092 up
->channel_oil
, pim
->regiface
,
1093 PIM_OIF_FLAG_PROTO_IGMP
);
1097 pim_channel_add_oif(up
->channel_oil
, pim
->regiface
,
1098 PIM_OIF_FLAG_PROTO_IGMP
);
1104 void pim_ifchannel_local_membership_del(struct interface
*ifp
,
1105 struct prefix_sg
*sg
)
1107 struct pim_ifchannel
*starch
, *ch
, *orig
;
1108 struct pim_interface
*pim_ifp
;
1110 /* PIM enabled on interface? */
1111 pim_ifp
= ifp
->info
;
1114 if (!PIM_IF_TEST_PIM(pim_ifp
->options
))
1117 orig
= ch
= pim_ifchannel_find(ifp
, sg
);
1121 ifmembership_set(ch
, PIM_IFMEMBERSHIP_NOINFO
);
1123 if (sg
->src
.s_addr
== INADDR_ANY
) {
1124 struct pim_upstream
*up
= pim_upstream_find(pim_ifp
->pim
, sg
);
1125 struct pim_upstream
*child
;
1126 struct listnode
*up_node
, *up_nnode
;
1130 for (ALL_LIST_ELEMENTS(up
->sources
, up_node
, up_nnode
, child
)) {
1131 struct channel_oil
*c_oil
= child
->channel_oil
;
1132 struct pim_ifchannel
*chchannel
=
1133 pim_ifchannel_find(ifp
, &child
->sg
);
1134 struct pim_interface
*pim_ifp
= ifp
->info
;
1136 if (PIM_DEBUG_EVENTS
)
1137 zlog_debug("%s %s: Prune(S,G)=%s(%s) from %s",
1138 __FILE__
, __PRETTY_FUNCTION__
,
1139 up
->sg_str
, ifp
->name
,
1142 ch
= pim_ifchannel_find(ifp
, &child
->sg
);
1144 && !pim_upstream_evaluate_join_desired_interface(
1146 pim_channel_del_oif(c_oil
, ifp
,
1147 PIM_OIF_FLAG_PROTO_STAR
);
1150 * If the S,G has no if channel and the c_oil still
1151 * has output here then the *,G was supplying the
1153 * if channel. So remove it.
1155 if (!chchannel
&& c_oil
1156 && c_oil
->oil
.mfcc_ttls
[pim_ifp
->mroute_vif_index
])
1157 pim_channel_del_oif(c_oil
, ifp
,
1158 PIM_OIF_FLAG_PROTO_STAR
);
1160 /* Child node removal/ref count-- will happen as part of
1161 * parent' delete_no_info */
1164 delete_on_noinfo(orig
);
1167 void pim_ifchannel_update_could_assert(struct pim_ifchannel
*ch
)
1169 int old_couldassert
=
1170 PIM_FORCE_BOOLEAN(PIM_IF_FLAG_TEST_COULD_ASSERT(ch
->flags
));
1171 int new_couldassert
=
1172 PIM_FORCE_BOOLEAN(pim_macro_ch_could_assert_eval(ch
));
1174 if (new_couldassert
== old_couldassert
)
1177 if (PIM_DEBUG_PIM_EVENTS
) {
1178 char src_str
[INET_ADDRSTRLEN
];
1179 char grp_str
[INET_ADDRSTRLEN
];
1180 pim_inet4_dump("<src?>", ch
->sg
.src
, src_str
, sizeof(src_str
));
1181 pim_inet4_dump("<grp?>", ch
->sg
.grp
, grp_str
, sizeof(grp_str
));
1182 zlog_debug("%s: CouldAssert(%s,%s,%s) changed from %d to %d",
1183 __PRETTY_FUNCTION__
, src_str
, grp_str
,
1184 ch
->interface
->name
, old_couldassert
,
1188 if (new_couldassert
) {
1189 /* CouldAssert(S,G,I) switched from FALSE to TRUE */
1190 PIM_IF_FLAG_SET_COULD_ASSERT(ch
->flags
);
1192 /* CouldAssert(S,G,I) switched from TRUE to FALSE */
1193 PIM_IF_FLAG_UNSET_COULD_ASSERT(ch
->flags
);
1195 if (ch
->ifassert_state
== PIM_IFASSERT_I_AM_WINNER
) {
1196 assert_action_a4(ch
);
1200 pim_ifchannel_update_my_assert_metric(ch
);
1204 my_assert_metric may be affected by:
1207 pim_ifp->primary_address
1208 rpf->source_nexthop.mrib_metric_preference;
1209 rpf->source_nexthop.mrib_route_metric;
1211 void pim_ifchannel_update_my_assert_metric(struct pim_ifchannel
*ch
)
1213 struct pim_assert_metric my_metric_new
=
1214 pim_macro_ch_my_assert_metric_eval(ch
);
1216 if (pim_assert_metric_match(&my_metric_new
, &ch
->ifassert_my_metric
))
1219 if (PIM_DEBUG_PIM_EVENTS
) {
1220 char src_str
[INET_ADDRSTRLEN
];
1221 char grp_str
[INET_ADDRSTRLEN
];
1222 char old_addr_str
[INET_ADDRSTRLEN
];
1223 char new_addr_str
[INET_ADDRSTRLEN
];
1224 pim_inet4_dump("<src?>", ch
->sg
.src
, src_str
, sizeof(src_str
));
1225 pim_inet4_dump("<grp?>", ch
->sg
.grp
, grp_str
, sizeof(grp_str
));
1226 pim_inet4_dump("<old_addr?>", ch
->ifassert_my_metric
.ip_address
,
1227 old_addr_str
, sizeof(old_addr_str
));
1228 pim_inet4_dump("<new_addr?>", my_metric_new
.ip_address
,
1229 new_addr_str
, sizeof(new_addr_str
));
1231 "%s: my_assert_metric(%s,%s,%s) changed from %u,%u,%u,%s to %u,%u,%u,%s",
1232 __PRETTY_FUNCTION__
, src_str
, grp_str
,
1233 ch
->interface
->name
,
1234 ch
->ifassert_my_metric
.rpt_bit_flag
,
1235 ch
->ifassert_my_metric
.metric_preference
,
1236 ch
->ifassert_my_metric
.route_metric
, old_addr_str
,
1237 my_metric_new
.rpt_bit_flag
,
1238 my_metric_new
.metric_preference
,
1239 my_metric_new
.route_metric
, new_addr_str
);
1242 ch
->ifassert_my_metric
= my_metric_new
;
1244 if (pim_assert_metric_better(&ch
->ifassert_my_metric
,
1245 &ch
->ifassert_winner_metric
)) {
1246 assert_action_a5(ch
);
1250 void pim_ifchannel_update_assert_tracking_desired(struct pim_ifchannel
*ch
)
1252 int old_atd
= PIM_FORCE_BOOLEAN(
1253 PIM_IF_FLAG_TEST_ASSERT_TRACKING_DESIRED(ch
->flags
));
1255 PIM_FORCE_BOOLEAN(pim_macro_assert_tracking_desired_eval(ch
));
1257 if (new_atd
== old_atd
)
1260 if (PIM_DEBUG_PIM_EVENTS
) {
1261 char src_str
[INET_ADDRSTRLEN
];
1262 char grp_str
[INET_ADDRSTRLEN
];
1263 pim_inet4_dump("<src?>", ch
->sg
.src
, src_str
, sizeof(src_str
));
1264 pim_inet4_dump("<grp?>", ch
->sg
.grp
, grp_str
, sizeof(grp_str
));
1266 "%s: AssertTrackingDesired(%s,%s,%s) changed from %d to %d",
1267 __PRETTY_FUNCTION__
, src_str
, grp_str
,
1268 ch
->interface
->name
, old_atd
, new_atd
);
1272 /* AssertTrackingDesired(S,G,I) switched from FALSE to TRUE */
1273 PIM_IF_FLAG_SET_ASSERT_TRACKING_DESIRED(ch
->flags
);
1275 /* AssertTrackingDesired(S,G,I) switched from TRUE to FALSE */
1276 PIM_IF_FLAG_UNSET_ASSERT_TRACKING_DESIRED(ch
->flags
);
1278 if (ch
->ifassert_state
== PIM_IFASSERT_I_AM_LOSER
) {
1279 assert_action_a5(ch
);
1285 * If we have a new pim interface, check to
1286 * see if any of the pre-existing channels have
1287 * their upstream out that way and turn on forwarding
1288 * for that ifchannel then.
1290 void pim_ifchannel_scan_forward_start(struct interface
*new_ifp
)
1292 struct listnode
*ifnode
;
1293 struct interface
*ifp
;
1294 struct pim_interface
*new_pim_ifp
= new_ifp
->info
;
1295 struct pim_instance
*pim
= new_pim_ifp
->pim
;
1297 for (ALL_LIST_ELEMENTS_RO(vrf_iflist(pim
->vrf_id
), ifnode
, ifp
)) {
1298 struct pim_interface
*loop_pim_ifp
= ifp
->info
;
1299 struct listnode
*ch_node
;
1300 struct pim_ifchannel
*ch
;
1305 if (new_pim_ifp
== loop_pim_ifp
)
1308 for (ALL_LIST_ELEMENTS_RO(loop_pim_ifp
->pim_ifchannel_list
,
1310 if (ch
->ifjoin_state
== PIM_IFJOIN_JOIN
) {
1311 struct pim_upstream
*up
= ch
->upstream
;
1312 if ((!up
->channel_oil
)
1313 && (up
->rpf
.source_nexthop
1314 .interface
== new_ifp
))
1315 pim_forward_start(ch
);
1322 * Downstream per-interface (S,G,rpt) state machine
1323 * states that we need to move (S,G,rpt) items
1324 * into different states at the start of the
1325 * reception of a *,G join as well, when
1326 * we get End of Message
1328 void pim_ifchannel_set_star_g_join_state(struct pim_ifchannel
*ch
, int eom
,
1329 uint8_t source_flags
, uint8_t join
,
1330 uint8_t starg_alone
)
1332 struct pim_ifchannel
*child
;
1333 struct listnode
*ch_node
;
1335 if (PIM_DEBUG_PIM_TRACE
)
1337 "%s: %s %s eom: %d join %u", __PRETTY_FUNCTION__
,
1338 pim_ifchannel_ifjoin_name(ch
->ifjoin_state
, ch
->flags
),
1339 ch
->sg_str
, eom
, join
);
1343 for (ALL_LIST_ELEMENTS_RO(ch
->sources
, ch_node
, child
)) {
1344 /* Only *,G Join received and no (SG-RPT) prune.
1345 eom = 1, only (W,G) join_alone is true, WC and RPT are set.
1346 Scan all S,G associated to G and if any SG-RPT
1347 remove the SG-RPT flag.
1349 if (eom
&& starg_alone
&& (source_flags
& PIM_RPT_BIT_MASK
)
1350 && (source_flags
& PIM_WILDCARD_BIT_MASK
)) {
1351 if (PIM_IF_FLAG_TEST_S_G_RPT(child
->flags
)) {
1352 struct pim_upstream
*up
= child
->upstream
;
1354 PIM_IF_FLAG_UNSET_S_G_RPT(child
->flags
);
1356 if (PIM_DEBUG_TRACE
)
1358 "%s: SGRpt flag is cleared, add inherit oif to up %s",
1359 __PRETTY_FUNCTION__
,
1361 pim_channel_add_oif(
1362 up
->channel_oil
, ch
->interface
,
1363 PIM_OIF_FLAG_PROTO_STAR
);
1364 pim_ifchannel_ifjoin_switch(
1365 __PRETTY_FUNCTION__
, child
,
1371 if (!PIM_IF_FLAG_TEST_S_G_RPT(child
->flags
))
1374 switch (child
->ifjoin_state
) {
1375 case PIM_IFJOIN_NOINFO
:
1376 case PIM_IFJOIN_JOIN
:
1378 case PIM_IFJOIN_PRUNE
:
1380 child
->ifjoin_state
= PIM_IFJOIN_PRUNE_TMP
;
1382 case PIM_IFJOIN_PRUNE_PENDING
:
1384 child
->ifjoin_state
=
1385 PIM_IFJOIN_PRUNE_PENDING_TMP
;
1387 case PIM_IFJOIN_PRUNE_TMP
:
1388 case PIM_IFJOIN_PRUNE_PENDING_TMP
:
1390 child
->ifjoin_state
= PIM_IFJOIN_NOINFO
;
1396 unsigned int pim_ifchannel_hash_key(void *arg
)
1398 struct pim_ifchannel
*ch
= (struct pim_ifchannel
*)arg
;
1400 return jhash_2words(ch
->sg
.src
.s_addr
, ch
->sg
.grp
.s_addr
, 0);
1403 int pim_ifchannel_equal(const void *arg1
, const void *arg2
)
1405 const struct pim_ifchannel
*ch1
= (const struct pim_ifchannel
*)arg1
;
1406 const struct pim_ifchannel
*ch2
= (const struct pim_ifchannel
*)arg2
;
1408 if ((ch1
->sg
.grp
.s_addr
== ch2
->sg
.grp
.s_addr
)
1409 && (ch1
->sg
.src
.s_addr
== ch2
->sg
.src
.s_addr
))