Exemplo n.º 1
0
/*
 * A new vif has come up -- update the children and leaf bitmaps in all route
 * entries to take that into account.
 */
void
add_vif_to_routes(vifi_t vifi)
{
    register struct rtentry *r;
    register struct uvif *v;

    v = &uvifs[vifi];
    for (r = routing_table; r != NULL; r = r->rt_next) {
        if (r->rt_metric != UNREACHABLE &&
                !VIFM_ISSET(vifi, r->rt_children)) {
            VIFM_SET(vifi, r->rt_children);
            r->rt_dominants   [vifi] = 0;
            r->rt_subordinates[vifi] = 0;
            if (v->uv_neighbors == NULL) {
                VIFM_SET(vifi, r->rt_leaves);
                r->rt_leaf_timers[vifi] = 0;
            }
            else {
                VIFM_CLR(vifi, r->rt_leaves);
                r->rt_leaf_timers[vifi] = LEAF_CONFIRMATION_TIME;
                r->rt_flags |= RTF_LEAF_TIMING;
            }
            update_table_entry(r);
        }
    }
}
Exemplo n.º 2
0
/*
 * A neighbor has failed or become unreachable.  If that neighbor was
 * considered a dominant or subordinate router in any route entries,
 * take appropriate action.
 */
void
delete_neighbor_from_routes(u_int32_t addr, vifi_t vifi)
{
    register struct rtentry *r;
    register struct uvif *v;

    v = &uvifs[vifi];
    for (r = routing_table; r != NULL; r = r->rt_next) {
        if (r->rt_metric != UNREACHABLE) {
            if (r->rt_dominants[vifi] == addr) {
                VIFM_SET(vifi, r->rt_children);
                r->rt_dominants   [vifi] = 0;
                r->rt_subordinates[vifi] = 0;
                if (v->uv_neighbors == NULL) {
                    VIFM_SET(vifi, r->rt_leaves);
                    r->rt_leaf_timers[vifi] = 0;
                }
                else {
                    VIFM_CLR(vifi, r->rt_leaves);
                    r->rt_leaf_timers[vifi] = LEAF_CONFIRMATION_TIME;
                    r->rt_flags |= RTF_LEAF_TIMING;
                }
                update_table_entry(r);
            }
            else if (r->rt_subordinates[vifi] == addr) {
                r->rt_subordinates[vifi] = 0;
                if (v->uv_neighbors == NULL) {
                    VIFM_SET(vifi, r->rt_leaves);
                    update_table_entry(r);
                }
                else {
                    r->rt_leaf_timers[vifi] = LEAF_CONFIRMATION_TIME;
                    r->rt_flags |= RTF_LEAF_TIMING;
                }
            }
            else if (v->uv_neighbors == NULL &&
                     r->rt_leaf_timers[vifi] != 0) {
                VIFM_SET(vifi, r->rt_leaves);
                r->rt_leaf_timers[vifi] = 0;
                update_table_entry(r);
            }
        }
    }
}
Exemplo n.º 3
0
/*
 * Initialize the children and leaf bits for route 'r', along with the
 * associated dominant, subordinate, and leaf timing data structures.
 * Return TRUE if this changes the value of either the children or
 * leaf bitmaps for 'r'.
 */
static int
init_children_and_leaves(struct rtentry *r, vifi_t parent)
{
    register vifi_t vifi;
    register struct uvif *v;
    vifbitmap_t old_children, old_leaves;

    VIFM_COPY(r->rt_children, old_children);
    VIFM_COPY(r->rt_leaves,   old_leaves  );

    VIFM_CLRALL(r->rt_children);
    VIFM_CLRALL(r->rt_leaves);
    r->rt_flags &= ~RTF_LEAF_TIMING;

    for (vifi = 0, v = uvifs; vifi < numvifs; ++vifi, ++v) {
        r->rt_dominants   [vifi] = 0;
        r->rt_subordinates[vifi] = 0;

        if (vifi != parent && !(v->uv_flags & (VIFF_DOWN|VIFF_DISABLED))) {
            VIFM_SET(vifi, r->rt_children);
            if (v->uv_neighbors == NULL) {
                VIFM_SET(vifi, r->rt_leaves);
                r->rt_leaf_timers[vifi] = 0;
            }
            else {
                r->rt_leaf_timers[vifi] = LEAF_CONFIRMATION_TIME;
                r->rt_flags |= RTF_LEAF_TIMING;
            }
        }
        else {
            r->rt_leaf_timers[vifi] = 0;
        }
    }

    return (!VIFM_SAME(r->rt_children, old_children) ||
            !VIFM_SAME(r->rt_leaves,   old_leaves));
}
Exemplo n.º 4
0
/*
 * On every timer interrupt, advance the timer in each routing entry.
 */
void
age_routes(void)
{
    register struct rtentry *r;
    register struct rtentry *prev_r;
    register vifi_t vifi;

    for (prev_r = RT_ADDR, r = routing_table;
            r != NULL;
            prev_r = r, r = r->rt_next) {

        if ((r->rt_timer += TIMER_INTERVAL) < ROUTE_EXPIRE_TIME) {
            /*
             * Route is still good; see if any leaf timers need to be
             * advanced.
             */
            if (r->rt_flags & RTF_LEAF_TIMING) {
                r->rt_flags &= ~RTF_LEAF_TIMING;
                for (vifi = 0; vifi < numvifs; ++vifi) {
                    if (r->rt_leaf_timers[vifi] != 0) {
                        /*
                         * Unlike other timers, leaf timers decrement.
                         */
                        if ((r->rt_leaf_timers[vifi] -= TIMER_INTERVAL) == 0) {
#ifdef NOTYET
                            /* If the vif is a physical leaf but has neighbors,
                             * it is not a tree leaf.  If I am a leaf, then no
                             * interface with neighbors is a tree leaf. */
                            if (!(((uvifs[vifi].uv_flags & VIFF_LEAF) ||
                                    (vifs_with_neighbors == 1)) &&
                                    (uvifs[vifi].uv_neighbors != NULL))) {
#endif
                                VIFM_SET(vifi, r->rt_leaves);
                                update_table_entry(r);
#ifdef NOTYET
                            }
#endif
                        }
                        else {
                            r->rt_flags |= RTF_LEAF_TIMING;
                        }
                    }
                }
            }
        }
        else if (r->rt_timer >= ROUTE_DISCARD_TIME) {
            /*
             * Time to garbage-collect the route entry.
             */
            del_table_entry(r, 0, DEL_ALL_ROUTES);
            discard_route(prev_r);
            r = prev_r;
        }
        else if (r->rt_metric != UNREACHABLE) {
            /*
             * Time to expire the route entry.  If the gateway is zero,
             * i.e., it is a route to a directly-connected subnet, just
             * set the timer back to zero; such routes expire only when
             * the interface to the subnet goes down.
             */
            if (r->rt_gateway == 0) {
                r->rt_timer = 0;
            }
            else {
                del_table_entry(r, 0, DEL_ALL_ROUTES);
                r->rt_metric   = UNREACHABLE;
                r->rt_flags   |= RTF_CHANGED;
                routes_changed = TRUE;
            }
        }
    }
}
Exemplo n.º 5
0
/*
 * Process a route report for a single origin, creating or updating the
 * corresponding routing table entry if necessary.  'src' is either the
 * address of a neighboring router from which the report arrived, or zero
 * to indicate a change of status of one of our own interfaces.
 */
void
update_route(u_int32_t origin, u_int32_t mask, u_int metric, u_int32_t src,
             vifi_t vifi)
{
    register struct rtentry *r;
    u_int adj_metric;

    /*
     * Compute an adjusted metric, taking into account the cost of the
     * subnet or tunnel over which the report arrived, and normalizing
     * all unreachable/poisoned metrics into a single value.
     */
    if (src != 0 && (metric < 1 || metric >= 2*UNREACHABLE)) {
        logit(LOG_WARNING, 0,
              "%s reports out-of-range metric %u for origin %s",
              inet_fmt(src, s1), metric, inet_fmts(origin, mask, s2));
        return;
    }
    adj_metric = metric + uvifs[vifi].uv_metric;
    if (adj_metric > UNREACHABLE) adj_metric = UNREACHABLE;

    /*
     * Look up the reported origin in the routing table.
     */
    if (!find_route(origin, mask)) {
        /*
         * Not found.
         * Don't create a new entry if the report says it's unreachable,
         * or if the reported origin and mask are invalid.
         */
        if (adj_metric == UNREACHABLE) {
            return;
        }
        if (src != 0 && !inet_valid_subnet(origin, mask)) {
            logit(LOG_WARNING, 0,
                  "%s reports an invalid origin (%s) and/or mask (%08x)",
                  inet_fmt(src, s1), inet_fmt(origin, s2), ntohl(mask));
            return;
        }

        /*
         * OK, create the new routing entry.  'rtp' will be left pointing
         * to the new entry.
         */
        create_route(origin, mask);

        /*
         * Now "steal away" any sources that belong under this route
         * by deleting any cache entries they might have created
         * and allowing the kernel to re-request them.
         */
        steal_sources(rtp);

        rtp->rt_metric = UNREACHABLE;	/* temporary; updated below */
    }

    /*
     * We now have a routing entry for the reported origin.  Update it?
     */
    r = rtp;
    if (r->rt_metric == UNREACHABLE) {
        /*
         * The routing entry is for a formerly-unreachable or new origin.
         * If the report claims reachability, update the entry to use
         * the reported route.
         */
        if (adj_metric == UNREACHABLE)
            return;

        r->rt_parent   = vifi;
        init_children_and_leaves(r, vifi);

        r->rt_gateway  = src;
        r->rt_timer    = 0;
        r->rt_metric   = adj_metric;
        r->rt_flags   |= RTF_CHANGED;
        routes_changed = TRUE;
        update_table_entry(r);
    }
    else if (src == r->rt_gateway) {
        /*
         * The report has come either from the interface directly-connected
         * to the origin subnet (src and r->rt_gateway both equal zero) or
         * from the gateway we have chosen as the best first-hop gateway back
         * towards the origin (src and r->rt_gateway not equal zero).  Reset
         * the route timer and, if the reported metric has changed, update
         * our entry accordingly.
         */
        r->rt_timer = 0;
        if (adj_metric == r->rt_metric)
            return;

        if (adj_metric == UNREACHABLE) {
            del_table_entry(r, 0, DEL_ALL_ROUTES);
            r->rt_timer = ROUTE_EXPIRE_TIME;
        }
        else if (adj_metric < r->rt_metric) {
            if (init_children_and_leaves(r, vifi)) {
                update_table_entry(r);
            }
        }
        r->rt_metric   = adj_metric;
        r->rt_flags   |= RTF_CHANGED;
        routes_changed = TRUE;
    }
    else if (src == 0 ||
             (r->rt_gateway != 0 &&
              (adj_metric < r->rt_metric ||
               (adj_metric == r->rt_metric &&
                (ntohl(src) < ntohl(r->rt_gateway) ||
                 r->rt_timer >= ROUTE_SWITCH_TIME))))) {
        /*
         * The report is for an origin we consider reachable; the report
         * comes either from one of our own interfaces or from a gateway
         * other than the one we have chosen as the best first-hop gateway
         * back towards the origin.  If the source of the update is one of
         * our own interfaces, or if the origin is not a directly-connected
         * subnet and the reported metric for that origin is better than
         * what our routing entry says, update the entry to use the new
         * gateway and metric.  We also switch gateways if the reported
         * metric is the same as the one in the route entry and the gateway
         * associated with the route entry has not been heard from recently,
         * or if the metric is the same but the reporting gateway has a lower
         * IP address than the gateway associated with the route entry.
         * Did you get all that?
         */
        if (r->rt_parent != vifi || adj_metric < r->rt_metric) {
            /*
             * XXX Why do we do this if we are just changing the metric?
             */
            r->rt_parent = vifi;
            if (init_children_and_leaves(r, vifi)) {
                update_table_entry(r);
            }
        }
        r->rt_gateway  = src;
        r->rt_timer    = 0;
        r->rt_metric   = adj_metric;
        r->rt_flags   |= RTF_CHANGED;
        routes_changed = TRUE;
    }
    else if (vifi != r->rt_parent) {
        /*
         * The report came from a vif other than the route's parent vif.
         * Update the children and leaf info, if necessary.
         */
        if (VIFM_ISSET(vifi, r->rt_children)) {
            /*
             * Vif is a child vif for this route.
             */
            if (metric  < r->rt_metric ||
                    (metric == r->rt_metric &&
                     ntohl(src) < ntohl(uvifs[vifi].uv_lcl_addr))) {
                /*
                 * Neighbor has lower metric to origin (or has same metric
                 * and lower IP address) -- it becomes the dominant router,
                 * and vif is no longer a child for me.
                 */
                VIFM_CLR(vifi, r->rt_children);
                VIFM_CLR(vifi, r->rt_leaves);
                r->rt_dominants   [vifi] = src;
                r->rt_subordinates[vifi] = 0;
                r->rt_leaf_timers [vifi] = 0;
                update_table_entry(r);
            }
            else if (metric > UNREACHABLE) {	/* "poisoned reverse" */
                /*
                 * Neighbor considers this vif to be on path to route's
                 * origin; if no subordinate recorded, record this neighbor
                 * as subordinate and clear the leaf flag.
                 */
                if (r->rt_subordinates[vifi] == 0) {
                    VIFM_CLR(vifi, r->rt_leaves);
                    r->rt_subordinates[vifi] = src;
                    r->rt_leaf_timers [vifi] = 0;
                    update_table_entry(r);
                }
            }
            else if (src == r->rt_subordinates[vifi]) {
                /*
                 * Current subordinate no longer considers this vif to be on
                 * path to route's origin; it is no longer a subordinate
                 * router, and we set the leaf confirmation timer to give
                 * us time to hear from other subordinates.
                 */
                r->rt_subordinates[vifi] = 0;
                if (uvifs[vifi].uv_neighbors == NULL ||
                        uvifs[vifi].uv_neighbors->al_next == NULL) {
                    VIFM_SET(vifi, r->rt_leaves);
                    update_table_entry(r);
                }
                else {
                    r->rt_leaf_timers [vifi] = LEAF_CONFIRMATION_TIME;
                    r->rt_flags |= RTF_LEAF_TIMING;
                }
            }

        }
        else if (src == r->rt_dominants[vifi] &&
                 (metric  > r->rt_metric ||
                  (metric == r->rt_metric &&
                   ntohl(src) > ntohl(uvifs[vifi].uv_lcl_addr)))) {
            /*
             * Current dominant no longer has a lower metric to origin
             * (or same metric and lower IP address); we adopt the vif
             * as our own child.
             */
            VIFM_SET(vifi, r->rt_children);
            r->rt_dominants  [vifi] = 0;
            if (metric > UNREACHABLE) {
                r->rt_subordinates[vifi] = src;
            }
            else if (uvifs[vifi].uv_neighbors == NULL ||
                     uvifs[vifi].uv_neighbors->al_next == NULL) {
                VIFM_SET(vifi, r->rt_leaves);
            }
            else {
                r->rt_leaf_timers[vifi] = LEAF_CONFIRMATION_TIME;
                r->rt_flags |= RTF_LEAF_TIMING;
            }
            update_table_entry(r);
        }
    }
}
Exemplo n.º 6
0
Arquivo: route.c Projeto: F0rth/pimd
/*
 * TODO: when cache miss, check the iif, because probably ASSERTS
 * shoult take place
 */
static void process_cache_miss(struct igmpmsg *igmpctl)
{
    u_int32 source, mfc_source;
    u_int32 group;
    u_int32 rp_addr;
    vifi_t iif;
    mrtentry_t *mrtentry_ptr;
    mrtentry_t *mrtentry_rp;

    /*
     * When there is a cache miss, we check only the header of the packet
     * (and only it should be sent up by the kernel.
     */

    group  = igmpctl->im_dst.s_addr;
    source = mfc_source = igmpctl->im_src.s_addr;
    iif    = igmpctl->im_vif;

    IF_DEBUG(DEBUG_MFC)
        logit(LOG_DEBUG, 0, "Cache miss, src %s, dst %s, iif %d",
              inet_fmt(source, s1, sizeof(s1)), inet_fmt(group, s2, sizeof(s2)), iif);

    /* TODO: XXX: check whether the kernel generates cache miss for the
     * LAN scoped addresses
     */
    if (ntohl(group) <= INADDR_MAX_LOCAL_GROUP)
        return; /* Don't create routing entries for the LAN scoped addresses */

    /* TODO: check if correct in case the source is one of my addresses */
    /* If I am the DR for this source, create (S,G) and add the register_vif
     * to the oifs.
     */
    if ((uvifs[iif].uv_flags & VIFF_DR)
        && (find_vif_direct_local(source) == iif)) {
        mrtentry_ptr = find_route(source, group, MRTF_SG, CREATE);
        if (mrtentry_ptr == (mrtentry_t *)NULL)
            return;
        mrtentry_ptr->flags &= ~MRTF_NEW;
        /* set reg_vif_num as outgoing interface ONLY if I am not the RP */
        if (mrtentry_ptr->group->rpaddr != my_cand_rp_address)
            VIFM_SET(reg_vif_num, mrtentry_ptr->joined_oifs);
        change_interfaces(mrtentry_ptr,
                          mrtentry_ptr->incoming,
                          mrtentry_ptr->joined_oifs,
                          mrtentry_ptr->pruned_oifs,
                          mrtentry_ptr->leaves,
                          mrtentry_ptr->asserted_oifs, 0);
    }
    else {
        mrtentry_ptr = find_route(source, group,
                                  MRTF_SG | MRTF_WC | MRTF_PMBR,
                                  DONT_CREATE);
        if (mrtentry_ptr == (mrtentry_t *)NULL)
            return;
    }

    /* TODO: if there are too many cache miss for the same (S,G), install
     * negative cache entry in the kernel (oif==NULL) to prevent too
     * many upcalls.
     */

    if (mrtentry_ptr->incoming == iif) {
        if (!VIFM_ISEMPTY(mrtentry_ptr->oifs)) {
            if (mrtentry_ptr->flags & MRTF_SG) {
                /* TODO: check that the RPbit is not set? */
                /* TODO: XXX: TIMER implem. dependency! */
                if (mrtentry_ptr->timer < PIM_DATA_TIMEOUT)
                    SET_TIMER(mrtentry_ptr->timer, PIM_DATA_TIMEOUT);
                if (!(mrtentry_ptr->flags & MRTF_SPT)) {
                    if ((mrtentry_rp = mrtentry_ptr->group->grp_route) ==
                        (mrtentry_t *)NULL)
                        mrtentry_rp = mrtentry_ptr->group->active_rp_grp->rp->rpentry->mrtlink;
                    if (mrtentry_rp != (mrtentry_t *)NULL) {
                        /* Check if the (S,G) iif is different from
                         * the (*,G) or (*,*,RP) iif
                         */
                        if ((mrtentry_ptr->incoming != mrtentry_rp->incoming)
                            || (mrtentry_ptr->upstream != mrtentry_rp->upstream)) {
                            mrtentry_ptr->flags |= MRTF_SPT;
                            mrtentry_ptr->flags &= ~MRTF_RP;
                        }
                    }
                }
            }
            if (mrtentry_ptr->flags & MRTF_PMBR)
                rp_addr = mrtentry_ptr->source->address;
            else
                rp_addr = mrtentry_ptr->group->rpaddr;
            mfc_source = source;
#ifdef KERNEL_MFC_WC_G
            if (mrtentry_ptr->flags & (MRTF_WC | MRTF_PMBR))
                if (!(mrtentry_ptr->flags & MRTF_MFC_CLONE_SG))
                    mfc_source = INADDR_ANY_N;
#endif /* KERNEL_MFC_WC_G */
            add_kernel_cache(mrtentry_ptr, mfc_source, group, MFC_MOVE_FORCE);
#ifdef SCOPED_ACL
            APPLY_SCOPE(group,mrtentry_ptr);
#endif
            k_chg_mfc(igmp_socket, mfc_source, group, iif, mrtentry_ptr->oifs,
                      rp_addr);
            /* TODO: XXX: No need for RSRR message, because nothing has
             * changed.
             */
        }

        return; /* iif match */
    }

    /* The iif doesn't match */
    if (mrtentry_ptr->flags & MRTF_SG) {
        if (mrtentry_ptr->flags & MRTF_SPT)
            /* Arrived on wrong interface */
            return;
        if ((mrtentry_rp = mrtentry_ptr->group->grp_route) ==
            (mrtentry_t *)NULL)
            mrtentry_rp =
                mrtentry_ptr->group->active_rp_grp->rp->rpentry->mrtlink;
        if (mrtentry_rp != (mrtentry_t *)NULL) {
            if (mrtentry_rp->incoming == iif) {
                /* Forward on (*,G) or (*,*,RP) */
#ifdef KERNEL_MFC_WC_G
                if (!(mrtentry_rp->flags & MRTF_MFC_CLONE_SG))
                    mfc_source = INADDR_ANY_N;
#endif /* KERNEL_MFC_WC_G */
                add_kernel_cache(mrtentry_rp, mfc_source, group, 0);
/* marian: not sure if we are going to reach here for our scoped traffic */
#ifdef SCOPED_ACL
                APPLY_SCOPE(group,mrtentry_ptr);
#endif
                k_chg_mfc(igmp_socket, mfc_source, group, iif,
                          mrtentry_rp->oifs, mrtentry_ptr->group->rpaddr);
#ifdef RSRR
                rsrr_cache_send(mrtentry_rp, RSRR_NOTIFICATION_OK);
#endif /* RSRR */
            }
        }

        return;
    }
}
Exemplo n.º 7
0
Arquivo: route.c Projeto: F0rth/pimd
/*
 * TODO: XXX: currently `source` is not used. Will be used with IGMPv3 where
 * we have source-specific Join/Prune.
 */
void add_leaf(vifi_t vifi, u_int32 source __attribute__((unused)), u_int32 group)
{
    mrtentry_t *mrtentry_ptr;
    mrtentry_t *mrtentry_srcs;
    vifbitmap_t old_oifs;
    vifbitmap_t new_oifs;
    vifbitmap_t new_leaves;

    if (ntohl(group) <= INADDR_MAX_LOCAL_GROUP)
        return; /* Don't create routing entries for the LAN scoped addresses */

    /*
     * XXX: only if I am a DR, the IGMP Join should result in creating
     * a PIM MRT state.
     * XXX: Each router must know if it has local members, i.e., whether
     * it is a last-hop router as well. This info is needed so it will
     * know whether is allowed to initiate a SPT switch by sending
     * a PIM (S,G) Join to the high datarate source.
     * However, if a non-DR last-hop router has not received
     * a PIM Join, it should not create a PIM state, otherwise later
     * this state may incorrectly trigger PIM joins.
     * There is a design flow in pimd, so without making major changes
     * the best we can do is that the non-DR last-hop router will
     * record the local members only after it receives PIM Join from the DR
     * (i.e.  after the second or third IGMP Join by the local member).
     * The downside is that a last-hop router may delay the initiation
     * of the SPT switch. Sigh...
     */
    if (uvifs[vifi].uv_flags & VIFF_DR)
        mrtentry_ptr = find_route(INADDR_ANY_N, group, MRTF_WC, CREATE);
    else
        mrtentry_ptr = find_route(INADDR_ANY_N, group, MRTF_WC, DONT_CREATE);

    if (mrtentry_ptr == (mrtentry_t *)NULL)
        return;

    IF_DEBUG(DEBUG_MRT)
        logit(LOG_DEBUG, 0, "Adding vif %d for group %s", vifi,
              inet_fmt(group, s1, sizeof(s1)));

    if (VIFM_ISSET(vifi, mrtentry_ptr->leaves))
        return;     /* Already a leaf */
    calc_oifs(mrtentry_ptr, &old_oifs);
    VIFM_COPY(mrtentry_ptr->leaves, new_leaves);
    VIFM_SET(vifi, new_leaves);    /* Add the leaf */
    change_interfaces(mrtentry_ptr,
                      mrtentry_ptr->incoming,
                      mrtentry_ptr->joined_oifs,
                      mrtentry_ptr->pruned_oifs,
                      new_leaves,
                      mrtentry_ptr->asserted_oifs, 0);
    calc_oifs(mrtentry_ptr, &new_oifs);

    /* Only if I am the DR for that subnet, eventually initiate a Join */
    if (!(uvifs[vifi].uv_flags & VIFF_DR))
        return;

    if ((mrtentry_ptr->flags & MRTF_NEW)
        || (VIFM_ISEMPTY(old_oifs) && (!VIFM_ISEMPTY(new_oifs)))) {
        /* A new created entry or the oifs have changed
         * from NULL to non-NULL.
         */
        mrtentry_ptr->flags &= ~MRTF_NEW;
        FIRE_TIMER(mrtentry_ptr->jp_timer); /* Timeout the Join/Prune timer */
        /* TODO: explicitly call the function below?
           send_pim_join_prune(mrtentry_ptr->upstream->vifi,
           mrtentry_ptr->upstream,
           PIM_JOIN_PRUNE_HOLDTIME);
        */
    }

    /* Check all (S,G) entries and set the inherited "leaf" flag.
     * TODO: XXX: This won't work for IGMPv3, because there we don't know
     * whether the (S,G) leaf oif was inherited from the (*,G) entry or
     * was created by source specific IGMP join.
     */
    for (mrtentry_srcs = mrtentry_ptr->group->mrtlink;
         mrtentry_srcs != (mrtentry_t *)NULL;
         mrtentry_srcs = mrtentry_srcs->grpnext) {
        VIFM_COPY(mrtentry_srcs->leaves, new_leaves);
        VIFM_SET(vifi, new_leaves);
        change_interfaces(mrtentry_srcs,
                          mrtentry_srcs->incoming,
                          mrtentry_srcs->joined_oifs,
                          mrtentry_srcs->pruned_oifs,
                          new_leaves,
                          mrtentry_srcs->asserted_oifs, 0);
    }
}