/* PIM for Quagga Copyright (C) 2008 Everton da Silva Marques This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA $QuaggaId: $Format:%an, %ai, %h$ $ */ #include #include "zebra/rib.h" #include "log.h" #include "zclient.h" #include "memory.h" #include "thread.h" #include "linklist.h" #include "pimd.h" #include "pim_pim.h" #include "pim_str.h" #include "pim_time.h" #include "pim_iface.h" #include "pim_join.h" #include "pim_zlookup.h" #include "pim_upstream.h" #include "pim_ifchannel.h" #include "pim_neighbor.h" #include "pim_rpf.h" #include "pim_zebra.h" #include "pim_oil.h" #include "pim_macro.h" static void join_timer_start(struct pim_upstream *up); static void pim_upstream_update_assert_tracking_desired(struct pim_upstream *up); void pim_upstream_free(struct pim_upstream *up) { XFREE(MTYPE_PIM_UPSTREAM, up); } static void upstream_channel_oil_detach(struct pim_upstream *up) { if (up->channel_oil) { pim_channel_oil_del(up->channel_oil); up->channel_oil = 0; } } void pim_upstream_delete(struct pim_upstream *up) { THREAD_OFF(up->t_join_timer); upstream_channel_oil_detach(up); /* notice that listnode_delete() can't be moved into pim_upstream_free() because the later is called by list_delete_all_node() */ listnode_delete(qpim_upstream_list, up); pim_upstream_free(up); } static void send_join(struct pim_upstream *up) { zassert(up->join_state == PIM_UPSTREAM_JOINED); if (PIM_DEBUG_PIM_TRACE) { if (PIM_INADDR_IS_ANY(up->rpf.rpf_addr)) { char src_str[100]; char grp_str[100]; char rpf_str[100]; pim_inet4_dump("", up->source_addr, src_str, sizeof(src_str)); pim_inet4_dump("", up->group_addr, grp_str, sizeof(grp_str)); pim_inet4_dump("", up->rpf.rpf_addr, rpf_str, sizeof(rpf_str)); zlog_warn("%s: can't send join upstream: RPF'(%s,%s)=%s", __PRETTY_FUNCTION__, src_str, grp_str, rpf_str); /* warning only */ } } /* send Join(S,G) to the current upstream neighbor */ pim_joinprune_send(up->rpf.source_nexthop.interface, up->rpf.rpf_addr, up->source_addr, up->group_addr, 1 /* join */); } static int on_join_timer(struct thread *t) { struct pim_upstream *up; zassert(t); up = THREAD_ARG(t); zassert(up); send_join(up); up->t_join_timer = 0; join_timer_start(up); return 0; } static void join_timer_start(struct pim_upstream *up) { if (PIM_DEBUG_PIM_EVENTS) { char src_str[100]; char grp_str[100]; pim_inet4_dump("", up->source_addr, src_str, sizeof(src_str)); pim_inet4_dump("", up->group_addr, grp_str, sizeof(grp_str)); zlog_debug("%s: starting %d sec timer for upstream (S,G)=(%s,%s)", __PRETTY_FUNCTION__, qpim_t_periodic, src_str, grp_str); } zassert(!up->t_join_timer); THREAD_TIMER_ON(master, up->t_join_timer, on_join_timer, up, qpim_t_periodic); } void pim_upstream_join_timer_restart(struct pim_upstream *up) { THREAD_OFF(up->t_join_timer); join_timer_start(up); } static void pim_upstream_join_timer_restart_msec(struct pim_upstream *up, int interval_msec) { if (PIM_DEBUG_PIM_EVENTS) { char src_str[100]; char grp_str[100]; pim_inet4_dump("", up->source_addr, src_str, sizeof(src_str)); pim_inet4_dump("", up->group_addr, grp_str, sizeof(grp_str)); zlog_debug("%s: restarting %d msec timer for upstream (S,G)=(%s,%s)", __PRETTY_FUNCTION__, interval_msec, src_str, grp_str); } THREAD_OFF(up->t_join_timer); THREAD_TIMER_MSEC_ON(master, up->t_join_timer, on_join_timer, up, interval_msec); } void pim_upstream_join_suppress(struct pim_upstream *up, struct in_addr rpf_addr, int holdtime) { long t_joinsuppress_msec; long join_timer_remain_msec; t_joinsuppress_msec = MIN(pim_if_t_suppressed_msec(up->rpf.source_nexthop.interface), 1000 * holdtime); join_timer_remain_msec = pim_time_timer_remain_msec(up->t_join_timer); if (PIM_DEBUG_PIM_TRACE) { char src_str[100]; char grp_str[100]; char rpf_str[100]; pim_inet4_dump("", up->source_addr, src_str, sizeof(src_str)); pim_inet4_dump("", up->group_addr, grp_str, sizeof(grp_str)); pim_inet4_dump("", rpf_addr, rpf_str, sizeof(rpf_str)); zlog_debug("%s %s: detected Join(%s,%s) to RPF'(S,G)=%s: join_timer=%ld msec t_joinsuppress=%ld msec", __FILE__, __PRETTY_FUNCTION__, src_str, grp_str, rpf_str, join_timer_remain_msec, t_joinsuppress_msec); } if (join_timer_remain_msec < t_joinsuppress_msec) { if (PIM_DEBUG_PIM_TRACE) { char src_str[100]; char grp_str[100]; pim_inet4_dump("", up->source_addr, src_str, sizeof(src_str)); pim_inet4_dump("", up->group_addr, grp_str, sizeof(grp_str)); zlog_debug("%s %s: suppressing Join(S,G)=(%s,%s) for %ld msec", __FILE__, __PRETTY_FUNCTION__, src_str, grp_str, t_joinsuppress_msec); } pim_upstream_join_timer_restart_msec(up, t_joinsuppress_msec); } } void pim_upstream_join_timer_decrease_to_t_override(const char *debug_label, struct pim_upstream *up, struct in_addr rpf_addr) { long join_timer_remain_msec; int t_override_msec; join_timer_remain_msec = pim_time_timer_remain_msec(up->t_join_timer); t_override_msec = pim_if_t_override_msec(up->rpf.source_nexthop.interface); if (PIM_DEBUG_PIM_TRACE) { char src_str[100]; char grp_str[100]; char rpf_str[100]; pim_inet4_dump("", up->source_addr, src_str, sizeof(src_str)); pim_inet4_dump("", up->group_addr, grp_str, sizeof(grp_str)); pim_inet4_dump("", rpf_addr, rpf_str, sizeof(rpf_str)); zlog_debug("%s: to RPF'(%s,%s)=%s: join_timer=%ld msec t_override=%d msec", debug_label, src_str, grp_str, rpf_str, join_timer_remain_msec, t_override_msec); } if (join_timer_remain_msec > t_override_msec) { if (PIM_DEBUG_PIM_TRACE) { char src_str[100]; char grp_str[100]; pim_inet4_dump("", up->source_addr, src_str, sizeof(src_str)); pim_inet4_dump("", up->group_addr, grp_str, sizeof(grp_str)); zlog_debug("%s: decreasing (S,G)=(%s,%s) join timer to t_override=%d msec", debug_label, src_str, grp_str, t_override_msec); } pim_upstream_join_timer_restart_msec(up, t_override_msec); } } static void forward_on(struct pim_upstream *up) { struct listnode *ifnode; struct listnode *ifnextnode; struct listnode *chnode; struct listnode *chnextnode; struct interface *ifp; struct pim_interface *pim_ifp; struct pim_ifchannel *ch; /* scan all interfaces */ for (ALL_LIST_ELEMENTS(iflist, ifnode, ifnextnode, ifp)) { pim_ifp = ifp->info; if (!pim_ifp) continue; /* scan per-interface (S,G) state */ for (ALL_LIST_ELEMENTS(pim_ifp->pim_ifchannel_list, chnode, chnextnode, ch)) { if (ch->upstream != up) continue; if (pim_macro_chisin_oiflist(ch)) pim_forward_start(ch); } /* scan iface channel list */ } /* scan iflist */ } static void forward_off(struct pim_upstream *up) { struct listnode *ifnode; struct listnode *ifnextnode; struct listnode *chnode; struct listnode *chnextnode; struct interface *ifp; struct pim_interface *pim_ifp; struct pim_ifchannel *ch; /* scan all interfaces */ for (ALL_LIST_ELEMENTS(iflist, ifnode, ifnextnode, ifp)) { pim_ifp = ifp->info; if (!pim_ifp) continue; /* scan per-interface (S,G) state */ for (ALL_LIST_ELEMENTS(pim_ifp->pim_ifchannel_list, chnode, chnextnode, ch)) { if (ch->upstream != up) continue; pim_forward_stop(ch); } /* scan iface channel list */ } /* scan iflist */ } static void pim_upstream_switch(struct pim_upstream *up, enum pim_upstream_state new_state) { enum pim_upstream_state old_state = up->join_state; zassert(old_state != new_state); up->join_state = new_state; up->state_transition = pim_time_monotonic_sec(); if (PIM_DEBUG_PIM_EVENTS) { char src_str[100]; char grp_str[100]; pim_inet4_dump("", up->source_addr, src_str, sizeof(src_str)); pim_inet4_dump("", up->group_addr, grp_str, sizeof(grp_str)); zlog_debug("%s: PIM_UPSTREAM_%s: (S,G)=(%s,%s)", __PRETTY_FUNCTION__, ((new_state == PIM_UPSTREAM_JOINED) ? "JOINED" : "NOTJOINED"), src_str, grp_str); } pim_upstream_update_assert_tracking_desired(up); if (new_state == PIM_UPSTREAM_JOINED) { forward_on(up); send_join(up); join_timer_start(up); } else { forward_off(up); pim_joinprune_send(up->rpf.source_nexthop.interface, up->rpf.rpf_addr, up->source_addr, up->group_addr, 0 /* prune */); zassert(up->t_join_timer); THREAD_OFF(up->t_join_timer); } } static struct pim_upstream *pim_upstream_new(struct in_addr source_addr, struct in_addr group_addr) { struct pim_upstream *up; enum pim_rpf_result rpf_result; up = XMALLOC(MTYPE_PIM_UPSTREAM, sizeof(*up)); if (!up) { zlog_err("%s: PIM XMALLOC(%zu) failure", __PRETTY_FUNCTION__, sizeof(*up)); return 0; } up->source_addr = source_addr; up->group_addr = group_addr; up->flags = 0; up->ref_count = 1; up->t_join_timer = 0; up->join_state = 0; up->state_transition = pim_time_monotonic_sec(); up->channel_oil = 0; up->rpf.source_nexthop.interface = 0; up->rpf.source_nexthop.mrib_nexthop_addr.s_addr = PIM_NET_INADDR_ANY; up->rpf.source_nexthop.mrib_metric_preference = qpim_infinite_assert_metric.metric_preference; up->rpf.source_nexthop.mrib_route_metric = qpim_infinite_assert_metric.route_metric; up->rpf.rpf_addr.s_addr = PIM_NET_INADDR_ANY; rpf_result = pim_rpf_update(up, 0); if (rpf_result == PIM_RPF_FAILURE) { XFREE(MTYPE_PIM_UPSTREAM, up); return NULL; } listnode_add(qpim_upstream_list, up); return up; } struct pim_upstream *pim_upstream_find(struct in_addr source_addr, struct in_addr group_addr) { struct listnode *up_node; struct pim_upstream *up; for (ALL_LIST_ELEMENTS_RO(qpim_upstream_list, up_node, up)) { if ( (source_addr.s_addr == up->source_addr.s_addr) && (group_addr.s_addr == up->group_addr.s_addr) ) { return up; } } return 0; } struct pim_upstream *pim_upstream_add(struct in_addr source_addr, struct in_addr group_addr) { struct pim_upstream *up; up = pim_upstream_find(source_addr, group_addr); if (up) { ++up->ref_count; } else { up = pim_upstream_new(source_addr, group_addr); } return up; } void pim_upstream_del(struct pim_upstream *up) { --up->ref_count; if (up->ref_count < 1) { pim_upstream_delete(up); } } /* Evaluate JoinDesired(S,G): JoinDesired(S,G) is true if there is a downstream (S,G) interface I in the set: inherited_olist(S,G) = joins(S,G) (+) pim_include(S,G) (-) lost_assert(S,G) JoinDesired(S,G) may be affected by changes in the following: pim_ifp->primary_address pim_ifp->pim_dr_addr ch->ifassert_winner_metric ch->ifassert_winner ch->local_ifmembership ch->ifjoin_state ch->upstream->rpf.source_nexthop.mrib_metric_preference ch->upstream->rpf.source_nexthop.mrib_route_metric ch->upstream->rpf.source_nexthop.interface See also pim_upstream_update_join_desired() below. */ int pim_upstream_evaluate_join_desired(struct pim_upstream *up) { struct listnode *ifnode; struct listnode *ifnextnode; struct listnode *chnode; struct listnode *chnextnode; struct interface *ifp; struct pim_interface *pim_ifp; struct pim_ifchannel *ch; /* scan all interfaces */ for (ALL_LIST_ELEMENTS(iflist, ifnode, ifnextnode, ifp)) { pim_ifp = ifp->info; if (!pim_ifp) continue; /* scan per-interface (S,G) state */ for (ALL_LIST_ELEMENTS(pim_ifp->pim_ifchannel_list, chnode, chnextnode, ch)) { if (ch->upstream != up) continue; if (pim_macro_ch_lost_assert(ch)) continue; /* keep searching */ if (pim_macro_chisin_joins_or_include(ch)) return 1; /* true */ } /* scan iface channel list */ } /* scan iflist */ return 0; /* false */ } /* See also pim_upstream_evaluate_join_desired() above. */ void pim_upstream_update_join_desired(struct pim_upstream *up) { int was_join_desired; /* boolean */ int is_join_desired; /* boolean */ was_join_desired = PIM_UPSTREAM_FLAG_TEST_DR_JOIN_DESIRED(up->flags); is_join_desired = pim_upstream_evaluate_join_desired(up); if (is_join_desired) PIM_UPSTREAM_FLAG_SET_DR_JOIN_DESIRED(up->flags); else PIM_UPSTREAM_FLAG_UNSET_DR_JOIN_DESIRED(up->flags); /* switched from false to true */ if (is_join_desired && !was_join_desired) { zassert(up->join_state == PIM_UPSTREAM_NOTJOINED); pim_upstream_switch(up, PIM_UPSTREAM_JOINED); return; } /* switched from true to false */ if (!is_join_desired && was_join_desired) { zassert(up->join_state == PIM_UPSTREAM_JOINED); pim_upstream_switch(up, PIM_UPSTREAM_NOTJOINED); return; } } /* RFC 4601 4.5.7. Sending (S,G) Join/Prune Messages Transitions from Joined State RPF'(S,G) GenID changes The upstream (S,G) state machine remains in Joined state. If the Join Timer is set to expire in more than t_override seconds, reset it so that it expires after t_override seconds. */ void pim_upstream_rpf_genid_changed(struct in_addr neigh_addr) { struct listnode *up_node; struct listnode *up_nextnode; struct pim_upstream *up; /* Scan all (S,G) upstreams searching for RPF'(S,G)=neigh_addr */ for (ALL_LIST_ELEMENTS(qpim_upstream_list, up_node, up_nextnode, up)) { if (PIM_DEBUG_PIM_TRACE) { char neigh_str[100]; char src_str[100]; char grp_str[100]; char rpf_addr_str[100]; pim_inet4_dump("", neigh_addr, neigh_str, sizeof(neigh_str)); pim_inet4_dump("", up->source_addr, src_str, sizeof(src_str)); pim_inet4_dump("", up->group_addr, grp_str, sizeof(grp_str)); pim_inet4_dump("", up->rpf.rpf_addr, rpf_addr_str, sizeof(rpf_addr_str)); zlog_debug("%s: matching neigh=%s against upstream (S,G)=(%s,%s) joined=%d rpf_addr=%s", __PRETTY_FUNCTION__, neigh_str, src_str, grp_str, up->join_state == PIM_UPSTREAM_JOINED, rpf_addr_str); } /* consider only (S,G) upstream in Joined state */ if (up->join_state != PIM_UPSTREAM_JOINED) continue; /* match RPF'(S,G)=neigh_addr */ if (up->rpf.rpf_addr.s_addr != neigh_addr.s_addr) continue; pim_upstream_join_timer_decrease_to_t_override("RPF'(S,G) GenID change", up, neigh_addr); } } void pim_upstream_rpf_interface_changed(struct pim_upstream *up, struct interface *old_rpf_ifp) { struct listnode *ifnode; struct listnode *ifnextnode; struct interface *ifp; /* scan all interfaces */ for (ALL_LIST_ELEMENTS(iflist, ifnode, ifnextnode, ifp)) { struct listnode *chnode; struct listnode *chnextnode; struct pim_ifchannel *ch; struct pim_interface *pim_ifp; pim_ifp = ifp->info; if (!pim_ifp) continue; /* search all ifchannels */ for (ALL_LIST_ELEMENTS(pim_ifp->pim_ifchannel_list, chnode, chnextnode, ch)) { if (ch->upstream != up) continue; if (ch->ifassert_state == PIM_IFASSERT_I_AM_LOSER) { if ( /* RPF_interface(S) was NOT I */ (old_rpf_ifp == ch->interface) && /* RPF_interface(S) stopped being I */ (ch->upstream->rpf.source_nexthop.interface != ch->interface) ) { assert_action_a5(ch); } } /* PIM_IFASSERT_I_AM_LOSER */ pim_ifchannel_update_assert_tracking_desired(ch); } } } void pim_upstream_update_could_assert(struct pim_upstream *up) { struct listnode *ifnode; struct listnode *ifnextnode; struct listnode *chnode; struct listnode *chnextnode; struct interface *ifp; struct pim_interface *pim_ifp; struct pim_ifchannel *ch; /* scan all interfaces */ for (ALL_LIST_ELEMENTS(iflist, ifnode, ifnextnode, ifp)) { pim_ifp = ifp->info; if (!pim_ifp) continue; /* scan per-interface (S,G) state */ for (ALL_LIST_ELEMENTS(pim_ifp->pim_ifchannel_list, chnode, chnextnode, ch)) { if (ch->upstream != up) continue; pim_ifchannel_update_could_assert(ch); } /* scan iface channel list */ } /* scan iflist */ } void pim_upstream_update_my_assert_metric(struct pim_upstream *up) { struct listnode *ifnode; struct listnode *ifnextnode; struct listnode *chnode; struct listnode *chnextnode; struct interface *ifp; struct pim_interface *pim_ifp; struct pim_ifchannel *ch; /* scan all interfaces */ for (ALL_LIST_ELEMENTS(iflist, ifnode, ifnextnode, ifp)) { pim_ifp = ifp->info; if (!pim_ifp) continue; /* scan per-interface (S,G) state */ for (ALL_LIST_ELEMENTS(pim_ifp->pim_ifchannel_list, chnode, chnextnode, ch)) { if (ch->upstream != up) continue; pim_ifchannel_update_my_assert_metric(ch); } /* scan iface channel list */ } /* scan iflist */ } static void pim_upstream_update_assert_tracking_desired(struct pim_upstream *up) { struct listnode *ifnode; struct listnode *ifnextnode; struct listnode *chnode; struct listnode *chnextnode; struct interface *ifp; struct pim_interface *pim_ifp; struct pim_ifchannel *ch; /* scan all interfaces */ for (ALL_LIST_ELEMENTS(iflist, ifnode, ifnextnode, ifp)) { pim_ifp = ifp->info; if (!pim_ifp) continue; /* scan per-interface (S,G) state */ for (ALL_LIST_ELEMENTS(pim_ifp->pim_ifchannel_list, chnode, chnextnode, ch)) { if (ch->upstream != up) continue; pim_ifchannel_update_assert_tracking_desired(ch); } /* scan iface channel list */ } /* scan iflist */ }