Merge tag 'upstream/1.2.4'
[quagga-debian.git] / pimd / pim_upstream.c
1 /*
2   PIM for Quagga
3   Copyright (C) 2008  Everton da Silva Marques
4
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.
9
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.
14   
15   You should have received a copy of the GNU General Public License
16   along with this program; see the file COPYING; if not, write to the
17   Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
18   MA 02110-1301 USA
19   
20   $QuaggaId: $Format:%an, %ai, %h$ $
21 */
22
23 #include <zebra.h>
24
25 #include "zebra/rib.h"
26
27 #include "log.h"
28 #include "zclient.h"
29 #include "memory.h"
30 #include "thread.h"
31 #include "linklist.h"
32
33 #include "pimd.h"
34 #include "pim_pim.h"
35 #include "pim_str.h"
36 #include "pim_time.h"
37 #include "pim_iface.h"
38 #include "pim_join.h"
39 #include "pim_zlookup.h"
40 #include "pim_upstream.h"
41 #include "pim_ifchannel.h"
42 #include "pim_neighbor.h"
43 #include "pim_rpf.h"
44 #include "pim_zebra.h"
45 #include "pim_oil.h"
46 #include "pim_macro.h"
47
48 static void join_timer_start(struct pim_upstream *up);
49 static void pim_upstream_update_assert_tracking_desired(struct pim_upstream *up);
50
51 void pim_upstream_free(struct pim_upstream *up)
52 {
53   XFREE(MTYPE_PIM_UPSTREAM, up);
54 }
55
56 static void upstream_channel_oil_detach(struct pim_upstream *up)
57 {
58   if (up->channel_oil) {
59     pim_channel_oil_del(up->channel_oil);
60     up->channel_oil = 0;
61   }
62 }
63
64 void pim_upstream_delete(struct pim_upstream *up)
65 {
66   THREAD_OFF(up->t_join_timer);
67
68   upstream_channel_oil_detach(up);
69
70   /*
71     notice that listnode_delete() can't be moved
72     into pim_upstream_free() because the later is
73     called by list_delete_all_node()
74   */
75   listnode_delete(qpim_upstream_list, up);
76
77   pim_upstream_free(up);
78 }
79
80 static void send_join(struct pim_upstream *up)
81 {
82   zassert(up->join_state == PIM_UPSTREAM_JOINED);
83
84   
85   if (PIM_DEBUG_PIM_TRACE) {
86     if (PIM_INADDR_IS_ANY(up->rpf.rpf_addr)) {
87       char src_str[100];
88       char grp_str[100];
89       char rpf_str[100];
90       pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
91       pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
92       pim_inet4_dump("<rpf?>", up->rpf.rpf_addr, rpf_str, sizeof(rpf_str));
93       zlog_warn("%s: can't send join upstream: RPF'(%s,%s)=%s",
94                 __PRETTY_FUNCTION__,
95                 src_str, grp_str, rpf_str);
96       /* warning only */
97     }
98   }
99   
100   /* send Join(S,G) to the current upstream neighbor */
101   pim_joinprune_send(up->rpf.source_nexthop.interface,
102                      up->rpf.rpf_addr,
103                      up->source_addr,
104                      up->group_addr,
105                      1 /* join */);
106 }
107
108 static int on_join_timer(struct thread *t)
109 {
110   struct pim_upstream *up;
111
112   zassert(t);
113   up = THREAD_ARG(t);
114   zassert(up);
115
116   send_join(up);
117
118   up->t_join_timer = 0;
119   join_timer_start(up);
120
121   return 0;
122 }
123
124 static void join_timer_start(struct pim_upstream *up)
125 {
126   if (PIM_DEBUG_PIM_EVENTS) {
127     char src_str[100];
128     char grp_str[100];
129     pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
130     pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
131     zlog_debug("%s: starting %d sec timer for upstream (S,G)=(%s,%s)",
132                __PRETTY_FUNCTION__,
133                qpim_t_periodic,
134                src_str, grp_str);
135   }
136
137   zassert(!up->t_join_timer);
138
139   THREAD_TIMER_ON(master, up->t_join_timer,
140                   on_join_timer,
141                   up, qpim_t_periodic);
142 }
143
144 void pim_upstream_join_timer_restart(struct pim_upstream *up)
145 {
146   THREAD_OFF(up->t_join_timer);
147   join_timer_start(up);
148 }
149
150 static void pim_upstream_join_timer_restart_msec(struct pim_upstream *up,
151                                                  int interval_msec)
152 {
153   if (PIM_DEBUG_PIM_EVENTS) {
154     char src_str[100];
155     char grp_str[100];
156     pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
157     pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
158     zlog_debug("%s: restarting %d msec timer for upstream (S,G)=(%s,%s)",
159                __PRETTY_FUNCTION__,
160                interval_msec,
161                src_str, grp_str);
162   }
163
164   THREAD_OFF(up->t_join_timer);
165   THREAD_TIMER_MSEC_ON(master, up->t_join_timer,
166                        on_join_timer,
167                        up, interval_msec);
168 }
169
170 void pim_upstream_join_suppress(struct pim_upstream *up,
171                                 struct in_addr rpf_addr,
172                                 int holdtime)
173 {
174   long t_joinsuppress_msec;
175   long join_timer_remain_msec;
176
177   t_joinsuppress_msec = MIN(pim_if_t_suppressed_msec(up->rpf.source_nexthop.interface),
178                             1000 * holdtime);
179
180   join_timer_remain_msec = pim_time_timer_remain_msec(up->t_join_timer);
181
182   if (PIM_DEBUG_PIM_TRACE) {
183     char src_str[100];
184     char grp_str[100];
185     char rpf_str[100];
186     pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
187     pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
188     pim_inet4_dump("<rpf?>", rpf_addr, rpf_str, sizeof(rpf_str));
189     zlog_debug("%s %s: detected Join(%s,%s) to RPF'(S,G)=%s: join_timer=%ld msec t_joinsuppress=%ld msec",
190                __FILE__, __PRETTY_FUNCTION__, 
191                src_str, grp_str,
192                rpf_str,
193                join_timer_remain_msec, t_joinsuppress_msec);
194   }
195
196   if (join_timer_remain_msec < t_joinsuppress_msec) {
197     if (PIM_DEBUG_PIM_TRACE) {
198       char src_str[100];
199       char grp_str[100];
200       pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
201       pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
202       zlog_debug("%s %s: suppressing Join(S,G)=(%s,%s) for %ld msec",
203                  __FILE__, __PRETTY_FUNCTION__, 
204                  src_str, grp_str, t_joinsuppress_msec);
205     }
206
207     pim_upstream_join_timer_restart_msec(up, t_joinsuppress_msec);
208   }
209 }
210
211 void pim_upstream_join_timer_decrease_to_t_override(const char *debug_label,
212                                                     struct pim_upstream *up,
213                                                     struct in_addr rpf_addr)
214 {
215   long join_timer_remain_msec;
216   int t_override_msec;
217
218   join_timer_remain_msec = pim_time_timer_remain_msec(up->t_join_timer);
219   t_override_msec = pim_if_t_override_msec(up->rpf.source_nexthop.interface);
220
221   if (PIM_DEBUG_PIM_TRACE) {
222     char src_str[100];
223     char grp_str[100];
224     char rpf_str[100];
225     pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
226     pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
227     pim_inet4_dump("<rpf?>", rpf_addr, rpf_str, sizeof(rpf_str));
228     zlog_debug("%s: to RPF'(%s,%s)=%s: join_timer=%ld msec t_override=%d msec",
229                debug_label,
230                src_str, grp_str, rpf_str,
231                join_timer_remain_msec, t_override_msec);
232   }
233     
234   if (join_timer_remain_msec > t_override_msec) {
235     if (PIM_DEBUG_PIM_TRACE) {
236       char src_str[100];
237       char grp_str[100];
238       pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
239       pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
240       zlog_debug("%s: decreasing (S,G)=(%s,%s) join timer to t_override=%d msec",
241                  debug_label,
242                  src_str, grp_str,
243                  t_override_msec);
244     }
245
246     pim_upstream_join_timer_restart_msec(up, t_override_msec);
247   }
248 }
249
250 static void forward_on(struct pim_upstream *up)
251 {
252   struct listnode      *ifnode;
253   struct listnode      *ifnextnode;
254   struct listnode      *chnode;
255   struct listnode      *chnextnode;
256   struct interface     *ifp;
257   struct pim_interface *pim_ifp;
258   struct pim_ifchannel *ch;
259
260   /* scan all interfaces */
261   for (ALL_LIST_ELEMENTS(iflist, ifnode, ifnextnode, ifp)) {
262     pim_ifp = ifp->info;
263     if (!pim_ifp)
264       continue;
265
266     /* scan per-interface (S,G) state */
267     for (ALL_LIST_ELEMENTS(pim_ifp->pim_ifchannel_list, chnode, chnextnode, ch)) {
268
269       if (ch->upstream != up)
270         continue;
271
272       if (pim_macro_chisin_oiflist(ch))
273         pim_forward_start(ch);
274
275     } /* scan iface channel list */
276   } /* scan iflist */
277 }
278
279 static void forward_off(struct pim_upstream *up)
280 {
281   struct listnode      *ifnode;
282   struct listnode      *ifnextnode;
283   struct listnode      *chnode;
284   struct listnode      *chnextnode;
285   struct interface     *ifp;
286   struct pim_interface *pim_ifp;
287   struct pim_ifchannel *ch;
288
289   /* scan all interfaces */
290   for (ALL_LIST_ELEMENTS(iflist, ifnode, ifnextnode, ifp)) {
291     pim_ifp = ifp->info;
292     if (!pim_ifp)
293       continue;
294
295     /* scan per-interface (S,G) state */
296     for (ALL_LIST_ELEMENTS(pim_ifp->pim_ifchannel_list, chnode, chnextnode, ch)) {
297
298       if (ch->upstream != up)
299         continue;
300
301       pim_forward_stop(ch);
302
303     } /* scan iface channel list */
304   } /* scan iflist */
305 }
306
307 static void pim_upstream_switch(struct pim_upstream *up,
308                                 enum pim_upstream_state new_state)
309 {
310   enum pim_upstream_state old_state = up->join_state;
311
312   zassert(old_state != new_state);
313   
314   up->join_state       = new_state;
315   up->state_transition = pim_time_monotonic_sec();
316
317   if (PIM_DEBUG_PIM_EVENTS) {
318     char src_str[100];
319     char grp_str[100];
320     pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
321     pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
322     zlog_debug("%s: PIM_UPSTREAM_%s: (S,G)=(%s,%s)",
323                __PRETTY_FUNCTION__,
324                ((new_state == PIM_UPSTREAM_JOINED) ? "JOINED" : "NOTJOINED"),
325                src_str, grp_str);
326   }
327
328   pim_upstream_update_assert_tracking_desired(up);
329
330   if (new_state == PIM_UPSTREAM_JOINED) {
331     forward_on(up);
332     send_join(up);
333     join_timer_start(up);
334   }
335   else {
336     forward_off(up);
337     pim_joinprune_send(up->rpf.source_nexthop.interface,
338                        up->rpf.rpf_addr,
339                        up->source_addr,
340                        up->group_addr,
341                        0 /* prune */);
342     zassert(up->t_join_timer);
343     THREAD_OFF(up->t_join_timer);
344   }
345
346 }
347
348 static struct pim_upstream *pim_upstream_new(struct in_addr source_addr,
349                                              struct in_addr group_addr)
350 {
351   struct pim_upstream *up;
352   enum pim_rpf_result rpf_result;
353
354   up = XMALLOC(MTYPE_PIM_UPSTREAM, sizeof(*up));
355   if (!up) {
356     zlog_err("%s: PIM XMALLOC(%zu) failure",
357              __PRETTY_FUNCTION__, sizeof(*up));
358     return 0;
359   }
360   
361   up->source_addr                = source_addr;
362   up->group_addr                 = group_addr;
363   up->flags                      = 0;
364   up->ref_count                  = 1;
365   up->t_join_timer               = 0;
366   up->join_state                 = 0;
367   up->state_transition           = pim_time_monotonic_sec();
368   up->channel_oil                = 0;
369
370   up->rpf.source_nexthop.interface                = 0;
371   up->rpf.source_nexthop.mrib_nexthop_addr.s_addr = PIM_NET_INADDR_ANY;
372   up->rpf.source_nexthop.mrib_metric_preference   = qpim_infinite_assert_metric.metric_preference;
373   up->rpf.source_nexthop.mrib_route_metric        = qpim_infinite_assert_metric.route_metric;
374   up->rpf.rpf_addr.s_addr                         = PIM_NET_INADDR_ANY;
375
376   rpf_result = pim_rpf_update(up, 0);
377   if (rpf_result == PIM_RPF_FAILURE) {
378     XFREE(MTYPE_PIM_UPSTREAM, up);
379     return NULL;
380   }
381
382   listnode_add(qpim_upstream_list, up);
383
384   return up;
385 }
386
387 struct pim_upstream *pim_upstream_find(struct in_addr source_addr,
388                                        struct in_addr group_addr)
389 {
390   struct listnode     *up_node;
391   struct pim_upstream *up;
392
393   for (ALL_LIST_ELEMENTS_RO(qpim_upstream_list, up_node, up)) {
394     if (
395         (source_addr.s_addr == up->source_addr.s_addr) &&
396         (group_addr.s_addr == up->group_addr.s_addr)
397         ) {
398       return up;
399     }
400   }
401
402   return 0;
403 }
404
405 struct pim_upstream *pim_upstream_add(struct in_addr source_addr,
406                                       struct in_addr group_addr)
407 {
408   struct pim_upstream *up;
409
410   up = pim_upstream_find(source_addr, group_addr);
411   if (up) {
412     ++up->ref_count;
413   }
414   else {
415     up = pim_upstream_new(source_addr, group_addr);
416   }
417
418   return up;
419 }
420
421 void pim_upstream_del(struct pim_upstream *up)
422 {
423   --up->ref_count;
424
425   if (up->ref_count < 1) {
426     pim_upstream_delete(up);
427   }
428 }
429
430 /*
431   Evaluate JoinDesired(S,G):
432
433   JoinDesired(S,G) is true if there is a downstream (S,G) interface I
434   in the set:
435
436   inherited_olist(S,G) =
437   joins(S,G) (+) pim_include(S,G) (-) lost_assert(S,G)
438
439   JoinDesired(S,G) may be affected by changes in the following:
440
441   pim_ifp->primary_address
442   pim_ifp->pim_dr_addr
443   ch->ifassert_winner_metric
444   ch->ifassert_winner
445   ch->local_ifmembership 
446   ch->ifjoin_state
447   ch->upstream->rpf.source_nexthop.mrib_metric_preference
448   ch->upstream->rpf.source_nexthop.mrib_route_metric
449   ch->upstream->rpf.source_nexthop.interface
450
451   See also pim_upstream_update_join_desired() below.
452  */
453 int pim_upstream_evaluate_join_desired(struct pim_upstream *up)
454 {
455   struct listnode      *ifnode;
456   struct listnode      *ifnextnode;
457   struct listnode      *chnode;
458   struct listnode      *chnextnode;
459   struct interface     *ifp;
460   struct pim_interface *pim_ifp;
461   struct pim_ifchannel *ch;
462
463   /* scan all interfaces */
464   for (ALL_LIST_ELEMENTS(iflist, ifnode, ifnextnode, ifp)) {
465     pim_ifp = ifp->info;
466     if (!pim_ifp)
467       continue;
468
469     /* scan per-interface (S,G) state */
470     for (ALL_LIST_ELEMENTS(pim_ifp->pim_ifchannel_list, chnode, chnextnode, ch)) {
471       if (ch->upstream != up)
472         continue;
473
474       if (pim_macro_ch_lost_assert(ch))
475         continue; /* keep searching */
476
477       if (pim_macro_chisin_joins_or_include(ch))
478         return 1; /* true */
479     } /* scan iface channel list */
480   } /* scan iflist */
481
482   return 0; /* false */
483 }
484
485 /*
486   See also pim_upstream_evaluate_join_desired() above.
487 */
488 void pim_upstream_update_join_desired(struct pim_upstream *up)
489 {
490   int was_join_desired; /* boolean */
491   int is_join_desired; /* boolean */
492
493   was_join_desired = PIM_UPSTREAM_FLAG_TEST_DR_JOIN_DESIRED(up->flags);
494
495   is_join_desired = pim_upstream_evaluate_join_desired(up);
496   if (is_join_desired)
497     PIM_UPSTREAM_FLAG_SET_DR_JOIN_DESIRED(up->flags);
498   else
499     PIM_UPSTREAM_FLAG_UNSET_DR_JOIN_DESIRED(up->flags);
500
501   /* switched from false to true */
502   if (is_join_desired && !was_join_desired) {
503     zassert(up->join_state == PIM_UPSTREAM_NOTJOINED);
504     pim_upstream_switch(up, PIM_UPSTREAM_JOINED);
505     return;
506   }
507       
508   /* switched from true to false */
509   if (!is_join_desired && was_join_desired) {
510     zassert(up->join_state == PIM_UPSTREAM_JOINED);
511     pim_upstream_switch(up, PIM_UPSTREAM_NOTJOINED);
512     return;
513   }
514 }
515
516 /*
517   RFC 4601 4.5.7. Sending (S,G) Join/Prune Messages
518   Transitions from Joined State
519   RPF'(S,G) GenID changes
520
521   The upstream (S,G) state machine remains in Joined state.  If the
522   Join Timer is set to expire in more than t_override seconds, reset
523   it so that it expires after t_override seconds.
524 */
525 void pim_upstream_rpf_genid_changed(struct in_addr neigh_addr)
526 {
527   struct listnode     *up_node;
528   struct listnode     *up_nextnode;
529   struct pim_upstream *up;
530
531   /*
532     Scan all (S,G) upstreams searching for RPF'(S,G)=neigh_addr
533   */
534   for (ALL_LIST_ELEMENTS(qpim_upstream_list, up_node, up_nextnode, up)) {
535
536     if (PIM_DEBUG_PIM_TRACE) {
537       char neigh_str[100];
538       char src_str[100];
539       char grp_str[100];
540       char rpf_addr_str[100];
541       pim_inet4_dump("<neigh?>", neigh_addr, neigh_str, sizeof(neigh_str));
542       pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
543       pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
544       pim_inet4_dump("<rpf?>", up->rpf.rpf_addr, rpf_addr_str, sizeof(rpf_addr_str));
545       zlog_debug("%s: matching neigh=%s against upstream (S,G)=(%s,%s) joined=%d rpf_addr=%s",
546                  __PRETTY_FUNCTION__,
547                  neigh_str, src_str, grp_str,
548                  up->join_state == PIM_UPSTREAM_JOINED,
549                  rpf_addr_str);
550     }
551
552     /* consider only (S,G) upstream in Joined state */
553     if (up->join_state != PIM_UPSTREAM_JOINED)
554       continue;
555
556     /* match RPF'(S,G)=neigh_addr */
557     if (up->rpf.rpf_addr.s_addr != neigh_addr.s_addr)
558       continue;
559
560     pim_upstream_join_timer_decrease_to_t_override("RPF'(S,G) GenID change",
561                                                    up, neigh_addr);
562   }
563 }
564
565
566 void pim_upstream_rpf_interface_changed(struct pim_upstream *up,
567                                         struct interface *old_rpf_ifp)
568 {
569   struct listnode  *ifnode;
570   struct listnode  *ifnextnode;
571   struct interface *ifp;
572
573   /* scan all interfaces */
574   for (ALL_LIST_ELEMENTS(iflist, ifnode, ifnextnode, ifp)) {
575     struct listnode      *chnode;
576     struct listnode      *chnextnode;
577     struct pim_ifchannel *ch;
578     struct pim_interface *pim_ifp;
579
580     pim_ifp = ifp->info;
581     if (!pim_ifp)
582       continue;
583
584     /* search all ifchannels */
585     for (ALL_LIST_ELEMENTS(pim_ifp->pim_ifchannel_list, chnode, chnextnode, ch)) {
586       if (ch->upstream != up)
587         continue;
588
589       if (ch->ifassert_state == PIM_IFASSERT_I_AM_LOSER) {
590         if (
591             /* RPF_interface(S) was NOT I */
592             (old_rpf_ifp == ch->interface)
593             &&
594             /* RPF_interface(S) stopped being I */
595             (ch->upstream->rpf.source_nexthop.interface != ch->interface)
596             ) {
597           assert_action_a5(ch);
598         }
599       } /* PIM_IFASSERT_I_AM_LOSER */
600
601       pim_ifchannel_update_assert_tracking_desired(ch);
602     }
603   }
604 }
605
606 void pim_upstream_update_could_assert(struct pim_upstream *up)
607 {
608   struct listnode      *ifnode;
609   struct listnode      *ifnextnode;
610   struct listnode      *chnode;
611   struct listnode      *chnextnode;
612   struct interface     *ifp;
613   struct pim_interface *pim_ifp;
614   struct pim_ifchannel *ch;
615
616   /* scan all interfaces */
617   for (ALL_LIST_ELEMENTS(iflist, ifnode, ifnextnode, ifp)) {
618     pim_ifp = ifp->info;
619     if (!pim_ifp)
620       continue;
621
622     /* scan per-interface (S,G) state */
623     for (ALL_LIST_ELEMENTS(pim_ifp->pim_ifchannel_list, chnode, chnextnode, ch)) {
624
625       if (ch->upstream != up)
626         continue;
627
628       pim_ifchannel_update_could_assert(ch);
629
630     } /* scan iface channel list */
631   } /* scan iflist */
632 }
633
634 void pim_upstream_update_my_assert_metric(struct pim_upstream *up)
635 {
636   struct listnode      *ifnode;
637   struct listnode      *ifnextnode;
638   struct listnode      *chnode;
639   struct listnode      *chnextnode;
640   struct interface     *ifp;
641   struct pim_interface *pim_ifp;
642   struct pim_ifchannel *ch;
643
644   /* scan all interfaces */
645   for (ALL_LIST_ELEMENTS(iflist, ifnode, ifnextnode, ifp)) {
646     pim_ifp = ifp->info;
647     if (!pim_ifp)
648       continue;
649
650     /* scan per-interface (S,G) state */
651     for (ALL_LIST_ELEMENTS(pim_ifp->pim_ifchannel_list, chnode, chnextnode, ch)) {
652
653       if (ch->upstream != up)
654         continue;
655
656       pim_ifchannel_update_my_assert_metric(ch);
657
658     } /* scan iface channel list */
659   } /* scan iflist */
660 }
661
662 static void pim_upstream_update_assert_tracking_desired(struct pim_upstream *up)
663 {
664   struct listnode      *ifnode;
665   struct listnode      *ifnextnode;
666   struct listnode      *chnode;
667   struct listnode      *chnextnode;
668   struct interface     *ifp;
669   struct pim_interface *pim_ifp;
670   struct pim_ifchannel *ch;
671
672   /* scan all interfaces */
673   for (ALL_LIST_ELEMENTS(iflist, ifnode, ifnextnode, ifp)) {
674     pim_ifp = ifp->info;
675     if (!pim_ifp)
676       continue;
677
678     /* scan per-interface (S,G) state */
679     for (ALL_LIST_ELEMENTS(pim_ifp->pim_ifchannel_list, chnode, chnextnode, ch)) {
680
681       if (ch->upstream != up)
682         continue;
683
684       pim_ifchannel_update_assert_tracking_desired(ch);
685
686     } /* scan iface channel list */
687   } /* scan iflist */
688 }