/*
* Copyright (c) 1998-2001
* University of Southern California/Information Sciences Institute.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the project nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/*
* $Id: route.c,v 1.1.1.1 2017/06/12 07:59:37 misho Exp $
*/
#include "defs.h"
/* Marian Stagarescu : 07/31/01:
*
* Administrative scoped multicast filtering im PIMD. This allows an
* interface to be configured as an administrative boundary for the
* specified scoped address. Packets belonging to the scoped address will
* not be forwarded.
*
* Please note the in order to minimize the search for the matching groups
* the implementation is limited to:
*
* Packets are stopped from being forwarded by installing a NULL outgoing
* interface; the user space (pimd) is not up-call-ed any more for
* these packets which are dropped by kernel (nil oif) except for
* when we de-install the route are re-create it (timer 3 minute).
* uses the VIF acl that was installed via config scoped statements.
*
* this is not an all-purpose packet filtering mechanism.
* we tried here to achieve the filtering with minimal processing
* (inspect (g) when we are about to install a route for it).
*
* to use it edit pimd.conf and compile with -DSCOPED_ACL
*/
static void process_cache_miss (struct igmpmsg *igmpctl);
static void process_wrong_iif (struct igmpmsg *igmpctl);
static void process_whole_pkt (char *buf);
#ifdef SCOPED_ACL
/* from mrouted. Contributed by Marian Stagarescu <marian@bile.cidera.com>*/
static int scoped_addr(vifi_t vifi, uint32_t addr)
{
struct vif_acl *acl;
for (acl = uvifs[vifi].uv_acl; acl; acl = acl->acl_next) {
if ((addr & acl->acl_mask) == acl->acl_addr)
return 1;
}
return 0;
}
/* Contributed by Marian Stagarescu <marian@bile.cidera.com>
* adapted from mrouted: check for scoped multicast addresses
* install null oif if matched
*/
#define APPLY_SCOPE(g, mp) { \
vifi_t i; \
for (i = 0; i < numvifs; i++) \
if (scoped_addr(i, g)) \
(mp)->oifs = 0; \
}
#endif /* SCOPED_ACL */
/* Return the iif for given address */
vifi_t get_iif(uint32_t address)
{
struct rpfctl rpfc;
k_req_incoming(address, &rpfc);
if (rpfc.rpfneighbor.s_addr == INADDR_ANY_N)
return NO_VIF;
return rpfc.iif;
}
/* Return the PIM neighbor toward a source */
/* If route not found or if a local source or if a directly connected source,
* but is not PIM router, or if the first hop router is not a PIM router,
* then return NULL.
*/
pim_nbr_entry_t *find_pim_nbr(uint32_t source)
{
struct rpfctl rpfc;
pim_nbr_entry_t *nbr;
uint32_t addr;
if (local_address(source) != NO_VIF)
return NULL;
k_req_incoming(source, &rpfc);
if ((rpfc.rpfneighbor.s_addr == INADDR_ANY_N) || (rpfc.iif == NO_VIF))
return NULL;
/* Figure out the nexthop neighbor by checking the reverse path */
addr = rpfc.rpfneighbor.s_addr;
for (nbr = uvifs[rpfc.iif].uv_pim_neighbors; nbr; nbr = nbr->next)
if (nbr->address == addr)
return nbr;
return NULL;
}
/* TODO: check again the exact setup if the source is local or directly
* connected!!!
*/
/* TODO: XXX: change the metric and preference for all (S,G) entries per
* source or RP?
*/
/* TODO - If possible, this would be the place to correct set the
* source's preference and metric to that obtained from the kernel
* and/or unicast routing protocol. For now, set it to the configured
* default for local pref/metric.
*/
/*
* Set the iif, upstream router, preference and metric for the route
* toward the source. Return TRUE is the route was found, othewise FALSE.
* If type==PIM_IIF_SOURCE and if the source is directly connected
* then the "upstream" is set to NULL. If srcentry==PIM_IIF_RP, then
* "upstream" in case of directly connected "source" will be that "source"
* (if it is also PIM router).,
*/
int set_incoming(srcentry_t *src, int type)
{
struct rpfctl rpfc;
uint32_t src_addr = src->address;
uint32_t nbr_addr;
struct uvif *vif;
pim_nbr_entry_t *nbr;
/* Preference will be 0 if directly connected */
src->metric = 0;
src->preference = 0;
/* The source is a local address */
src->incoming = local_address(src_addr);
if (src->incoming != NO_VIF) {
/* iif of (*,G) at RP has to be register_if */
if (type == PIM_IIF_RP)
src->incoming = reg_vif_num;
/* TODO: set the upstream to myself? */
src->upstream = NULL;
return TRUE;
}
src->incoming = find_vif_direct(src_addr);
if (src->incoming != NO_VIF) {
/* The source is directly connected. Check whether we are
* looking for real source or RP */
if (type == PIM_IIF_SOURCE) {
src->upstream = NULL;
return TRUE;
}
/* PIM_IIF_RP */
nbr_addr = src_addr;
} else {
/* TODO: probably need to check the case if the iif is disabled */
/* Use the lastest resource: the kernel unicast routing table */
k_req_incoming(src_addr, &rpfc);
if ((rpfc.iif == NO_VIF) || rpfc.rpfneighbor.s_addr == INADDR_ANY_N) {
/* couldn't find a route */
if (!IN_LINK_LOCAL_RANGE(src_addr)) {
IF_DEBUG(DEBUG_PIM_MRT | DEBUG_RPF)
logit(LOG_DEBUG, 0, "NO ROUTE found for %s", inet_fmt(src_addr, s1, sizeof(s1)));
}
return FALSE;
}
src->incoming = rpfc.iif;
nbr_addr = rpfc.rpfneighbor.s_addr;
/* set the preference for sources that aren't directly connected. */
vif = &uvifs[src->incoming];
src->preference = vif->uv_local_pref;
src->metric = vif->uv_local_metric;
}
/* The upstream router must be a (PIM router) neighbor, otherwise we
* are in big trouble ;-) */
vif = &uvifs[src->incoming];
for (nbr = vif->uv_pim_neighbors; nbr; nbr = nbr->next) {
if (ntohl(nbr_addr) < ntohl(nbr->address))
continue;
if (nbr_addr == nbr->address) {
/* The upstream router is found in the list of neighbors.
* We are safe! */
src->upstream = nbr;
IF_DEBUG(DEBUG_RPF)
logit(LOG_DEBUG, 0, "For src %s, iif is %d, next hop router is %s",
inet_fmt(src_addr, s1, sizeof(s1)), src->incoming,
inet_fmt(nbr_addr, s2, sizeof(s2)));
return TRUE;
}
break;
}
/* TODO: control the number of messages! */
logit(LOG_INFO, 0, "For src %s, iif is %d, next hop router is %s: NOT A PIM ROUTER",
inet_fmt(src_addr, s1, sizeof(s1)), src->incoming,
inet_fmt(nbr_addr, s2, sizeof(s2)));
src->upstream = NULL;
return FALSE;
}
/*
* 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, uint32_t source, uint32_t group)
{
mrtentry_t *mrt;
mrtentry_t *srcs;
vifbitmap_t old_oifs;
vifbitmap_t new_oifs;
vifbitmap_t new_leaves;
/* Don't create routing entries for the LAN scoped addresses */
if (ntohl(group) <= INADDR_MAX_LOCAL_GROUP) { /* group <= 224.0.0.255? */
IF_DEBUG(DEBUG_IGMP)
logit(LOG_DEBUG, 0, "Not creating routing entry for LAN scoped group %s",
inet_fmt(group, s1, sizeof(s1)));
return;
}
/*
* 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 (IN_PIM_SSM_RANGE(group))
mrt = find_route(source, group, MRTF_SG, CREATE);
else if (uvifs[vifi].uv_flags & VIFF_DR)
mrt = find_route(INADDR_ANY_N, group, MRTF_WC, CREATE);
else
mrt = find_route(INADDR_ANY_N, group, MRTF_WC, DONT_CREATE);
if (!mrt)
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, mrt->leaves))
return; /* Already a leaf */
calc_oifs(mrt, &old_oifs);
VIFM_COPY(mrt->leaves, new_leaves);
VIFM_SET(vifi, new_leaves); /* Add the leaf */
change_interfaces(mrt,
mrt->incoming,
mrt->joined_oifs,
mrt->pruned_oifs,
new_leaves,
mrt->asserted_oifs, 0);
calc_oifs(mrt, &new_oifs);
/* Only if I am the DR for that subnet, eventually initiate a Join */
if (!(uvifs[vifi].uv_flags & VIFF_DR))
return;
if ((mrt->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. */
mrt->flags &= ~MRTF_NEW;
FIRE_TIMER(mrt->jp_timer); /* Timeout the Join/Prune timer */
/* TODO: explicitly call the function below?
send_pim_join_prune(mrt->upstream->vifi,
mrt->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 (srcs = mrt->group->mrtlink; srcs; srcs = srcs->grpnext) {
VIFM_COPY(srcs->leaves, new_leaves);
VIFM_SET(vifi, new_leaves);
change_interfaces(srcs,
srcs->incoming,
srcs->joined_oifs,
srcs->pruned_oifs,
new_leaves,
srcs->asserted_oifs, 0);
}
}
/*
* TODO: XXX: currently `source` is not used. To be used with IGMPv3 where
* we have source-specific joins/prunes.
*/
void delete_leaf(vifi_t vifi, uint32_t source, uint32_t group)
{
mrtentry_t *mrt;
mrtentry_t *srcs;
vifbitmap_t new_oifs;
vifbitmap_t old_oifs;
vifbitmap_t new_leaves;
if (IN_PIM_SSM_RANGE(group))
mrt = find_route(source, group, MRTF_SG, DONT_CREATE);
else
mrt = find_route(INADDR_ANY_N, group, MRTF_WC, DONT_CREATE);
if (!mrt)
return;
if (!VIFM_ISSET(vifi, mrt->leaves))
return; /* This interface wasn't leaf */
IF_DEBUG(DEBUG_MRT)
logit(LOG_DEBUG, 0, "Deleting vif %d for group %s", vifi, inet_fmt(group, s1, sizeof(s1)));
calc_oifs(mrt, &old_oifs);
/* For SSM, source must match */
if (!IN_PIM_SSM_RANGE(group) || (mrt->source->address==source)) {
VIFM_COPY(mrt->leaves, new_leaves);
VIFM_CLR(vifi, new_leaves);
change_interfaces(mrt,
mrt->incoming,
mrt->joined_oifs,
mrt->pruned_oifs,
new_leaves,
mrt->asserted_oifs, 0);
}
calc_oifs(mrt, &new_oifs);
if ((!VIFM_ISEMPTY(old_oifs)) && VIFM_ISEMPTY(new_oifs)) {
/* The result oifs have changed from non-NULL to NULL */
FIRE_TIMER(mrt->jp_timer); /* Timeout the Join/Prune timer */
/* TODO: explicitly call the function below?
send_pim_join_prune(mrt->upstream->vifi,
mrt->upstream,
PIM_JOIN_PRUNE_HOLDTIME);
*/
}
/* Check all (S,G) entries and clear 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 (srcs = mrt->group->mrtlink; srcs; srcs = srcs->grpnext) {
VIFM_COPY(srcs->leaves, new_leaves);
VIFM_CLR(vifi, new_leaves);
change_interfaces(srcs,
srcs->incoming,
srcs->joined_oifs,
srcs->pruned_oifs,
new_leaves,
srcs->asserted_oifs, 0);
}
}
void calc_oifs(mrtentry_t *mrt, vifbitmap_t *oifs_ptr)
{
vifbitmap_t oifs;
mrtentry_t *grp;
mrtentry_t *mrp;
/*
* oifs =
* (((copied_outgoing + my_join) - my_prune) + my_leaves)
* - my_asserted_oifs - incoming_interface,
* i.e. `leaves` have higher priority than `prunes`, but lower priority
* than `asserted`. The incoming interface is always deleted from the oifs
*/
if (!mrt) {
VIFM_CLRALL(*oifs_ptr);
return;
}
VIFM_CLRALL(oifs);
if (!(mrt->flags & MRTF_PMBR)) {
/* Either (*,G) or (S,G). Merge with the oifs from the (*,*,RP) */
mrp = mrt->group->active_rp_grp->rp->rpentry->mrtlink;
if (mrp) {
VIFM_MERGE(oifs, mrp->joined_oifs, oifs);
VIFM_CLR_MASK(oifs, mrp->pruned_oifs);
VIFM_MERGE(oifs, mrp->leaves, oifs);
VIFM_CLR_MASK(oifs, mrp->asserted_oifs);
}
}
if (mrt->flags & MRTF_SG) {
/* (S,G) entry. Merge with the oifs from (*,G) */
grp = mrt->group->grp_route;
if (grp) {
VIFM_MERGE(oifs, grp->joined_oifs, oifs);
VIFM_CLR_MASK(oifs, grp->pruned_oifs);
VIFM_MERGE(oifs, grp->leaves, oifs);
VIFM_CLR_MASK(oifs, grp->asserted_oifs);
}
}
/* Calculate my own stuff */
VIFM_MERGE(oifs, mrt->joined_oifs, oifs);
VIFM_CLR_MASK(oifs, mrt->pruned_oifs);
VIFM_MERGE(oifs, mrt->leaves, oifs);
VIFM_CLR_MASK(oifs, mrt->asserted_oifs);
VIFM_COPY(oifs, *oifs_ptr);
}
/*
* Set the iif, join/prune/leaves/asserted interfaces. Calculate and
* set the oifs.
* Return 1 if oifs change from NULL to not-NULL.
* Return -1 if oifs change from non-NULL to NULL
* else return 0
* If the iif change or if the oifs change from NULL to non-NULL
* or vice-versa, then schedule that mrtentry join/prune timer to
* timeout immediately.
*/
int change_interfaces(mrtentry_t *mrt,
vifi_t new_iif,
vifbitmap_t new_joined_oifs_,
vifbitmap_t new_pruned_oifs,
vifbitmap_t new_leaves_,
vifbitmap_t new_asserted_oifs,
uint16_t flags)
{
vifbitmap_t new_joined_oifs; /* The oifs for that particular mrtentry */
vifbitmap_t old_joined_oifs __attribute__ ((unused));
vifbitmap_t old_pruned_oifs __attribute__ ((unused));
vifbitmap_t old_leaves __attribute__ ((unused));
vifbitmap_t new_leaves;
vifbitmap_t old_asserted_oifs __attribute__ ((unused));
vifbitmap_t new_real_oifs; /* The result oifs */
vifbitmap_t old_real_oifs;
vifi_t old_iif;
rpentry_t *rp;
cand_rp_t *cand_rp;
kernel_cache_t *kc;
rp_grp_entry_t *rp_grp;
grpentry_t *grp;
mrtentry_t *srcs;
mrtentry_t *mwc;
mrtentry_t *mrp;
int delete_mrt_flag;
int result;
int fire_timer_flag;
if (!mrt)
return 0;
VIFM_COPY(new_joined_oifs_, new_joined_oifs);
VIFM_COPY(new_leaves_, new_leaves);
old_iif = mrt->incoming;
VIFM_COPY(mrt->joined_oifs, old_joined_oifs);
VIFM_COPY(mrt->leaves, old_leaves);
VIFM_COPY(mrt->pruned_oifs, old_pruned_oifs);
VIFM_COPY(mrt->asserted_oifs, old_asserted_oifs);
VIFM_COPY(mrt->oifs, old_real_oifs);
mrt->incoming = new_iif;
VIFM_COPY(new_joined_oifs, mrt->joined_oifs);
VIFM_COPY(new_pruned_oifs, mrt->pruned_oifs);
VIFM_COPY(new_leaves, mrt->leaves);
VIFM_COPY(new_asserted_oifs, mrt->asserted_oifs);
calc_oifs(mrt, &new_real_oifs);
if (VIFM_ISEMPTY(old_real_oifs)) {
if (VIFM_ISEMPTY(new_real_oifs))
result = 0;
else
result = 1;
} else {
if (VIFM_ISEMPTY(new_real_oifs))
result = -1;
else
result = 0;
}
if ((VIFM_SAME(new_real_oifs, old_real_oifs))
&& (new_iif == old_iif)
&& !(flags & MFC_UPDATE_FORCE))
return 0; /* Nothing to change */
if ((result != 0) || (new_iif != old_iif) || (flags & MFC_UPDATE_FORCE)) {
FIRE_TIMER(mrt->jp_timer);
}
VIFM_COPY(new_real_oifs, mrt->oifs);
if (mrt->flags & MRTF_PMBR) {
/* (*,*,RP) entry */
rp = mrt->source;
if (!rp)
return 0; /* Shouldn't happen */
rp->incoming = new_iif;
cand_rp = rp->cand_rp;
if (VIFM_ISEMPTY(new_real_oifs)) {
delete_mrt_flag = TRUE;
} else {
delete_mrt_flag = FALSE;
#ifdef RSRR
rsrr_cache_send(mrt, RSRR_NOTIFICATION_OK);
#endif /* RSRR */
}
if (mrt->flags & MRTF_KERNEL_CACHE) {
/* Update the kernel MFC entries */
if (delete_mrt_flag == TRUE) {
/* XXX: no need to send RSRR message. Will do it when
* delete the mrtentry.
*/
for (kc = mrt->kernel_cache; kc; kc = kc->next)
delete_mrtentry_all_kernel_cache(mrt);
} else {
/* here mrt->source->address is the RP address */
for (kc = mrt->kernel_cache; kc; kc = kc->next)
k_chg_mfc(igmp_socket, kc->source,
kc->group, new_iif,
new_real_oifs, mrt->source->address);
}
}
/*
* Update all (*,G) entries associated with this RP.
* The particular (*,G) outgoing are not changed, but the change
* in the (*,*,RP) oifs may have affect the real oifs.
*/
fire_timer_flag = FALSE;
for (rp_grp = cand_rp->rp_grp_next; rp_grp; rp_grp = rp_grp->rp_grp_next) {
for (grp = rp_grp->grplink; grp; grp = grp->rpnext) {
if (grp->grp_route) {
if (change_interfaces(grp->grp_route, new_iif,
grp->grp_route->joined_oifs,
grp->grp_route->pruned_oifs,
grp->grp_route->leaves,
grp->grp_route->asserted_oifs,
flags))
fire_timer_flag = TRUE;
} else {
/* Change all (S,G) entries if no (*,G) */
for (srcs = grp->mrtlink; srcs; srcs = srcs->grpnext) {
if (srcs->flags & MRTF_RP) {
if (change_interfaces(srcs, new_iif,
srcs->joined_oifs,
srcs->pruned_oifs,
srcs->leaves,
srcs->asserted_oifs,
flags))
fire_timer_flag = TRUE;
} else {
if (change_interfaces(srcs,
srcs->incoming,
srcs->joined_oifs,
srcs->pruned_oifs,
srcs->leaves,
srcs->asserted_oifs,
flags))
fire_timer_flag = TRUE;
}
}
}
}
}
if (fire_timer_flag == TRUE)
FIRE_TIMER(mrt->jp_timer);
if (delete_mrt_flag == TRUE) {
/* TODO: XXX: trigger a Prune message? Don't delete now, it will
* be automatically timed out. If want to delete now, don't
* reference to it anymore!
delete_mrtentry(mrt);
*/
}
return result; /* (*,*,RP) */
}
if (mrt->flags & MRTF_WC) {
/* (*,G) entry */
if (VIFM_ISEMPTY(new_real_oifs)) {
delete_mrt_flag = TRUE;
} else {
delete_mrt_flag = FALSE;
#ifdef RSRR
rsrr_cache_send(mrt, RSRR_NOTIFICATION_OK);
#endif /* RSRR */
}
if (mrt->flags & MRTF_KERNEL_CACHE) {
if (delete_mrt_flag == TRUE) {
delete_mrtentry_all_kernel_cache(mrt);
} else {
for (kc = mrt->kernel_cache; kc; kc = kc->next)
k_chg_mfc(igmp_socket, kc->source,
kc->group, new_iif,
new_real_oifs, mrt->group->rpaddr);
}
}
/* Update all (S,G) entries for this group.
* For the (S,G)RPbit entries the iif is the iif toward the RP;
* The particular (S,G) oifs are not changed, but the change in the
* (*,G) oifs may affect the real oifs.
*/
fire_timer_flag = FALSE;
for (srcs = mrt->group->mrtlink; srcs; srcs = srcs->grpnext) {
if (srcs->flags & MRTF_RP) {
if (change_interfaces(srcs, new_iif,
srcs->joined_oifs,
srcs->pruned_oifs,
srcs->leaves,
srcs->asserted_oifs, flags))
fire_timer_flag = TRUE;
} else {
if (change_interfaces(srcs, srcs->incoming,
srcs->joined_oifs,
srcs->pruned_oifs,
srcs->leaves,
srcs->asserted_oifs, flags))
fire_timer_flag = TRUE;
}
}
if (fire_timer_flag == TRUE)
FIRE_TIMER(mrt->jp_timer);
if (delete_mrt_flag == TRUE) {
/* TODO: XXX: the oifs are NULL. Send a Prune message? */
}
return result; /* (*,G) */
}
/* (S,G) entry */
if (mrt->flags & MRTF_SG) {
mrp = mrt->group->active_rp_grp->rp->rpentry->mrtlink;
mwc = mrt->group->grp_route;
#ifdef KERNEL_MFC_WC_G
mrtentry_t *tmp;
/* Check whether (*,*,RP) or (*,G) have different (iif,oifs) from
* the (S,G). If "yes", then forbid creating (*,G) MFC. */
for (tmp = mrp; 1; tmp = mwc) {
while (1) {
vifbitmap_t oifs;
if (!tmp)
break;
if (tmp->flags & MRTF_MFC_CLONE_SG)
break;
if (tmp->incoming != mrt->incoming) {
delete_single_kernel_cache_addr(tmp, INADDR_ANY_N, mrt->group->group);
tmp->flags |= MRTF_MFC_CLONE_SG;
break;
}
calc_oifs(tmp, &oifs);
if (!(VIFM_SAME(new_real_oifs, oifs)))
tmp->flags |= MRTF_MFC_CLONE_SG;
break;
}
if (tmp == mwc)
break;
}
#endif /* KERNEL_MFC_WC_G */
if (VIFM_ISEMPTY(new_real_oifs)) {
delete_mrt_flag = TRUE;
} else {
delete_mrt_flag = FALSE;
#ifdef RSRR
rsrr_cache_send(mrt, RSRR_NOTIFICATION_OK);
#endif
}
if (mrt->flags & MRTF_KERNEL_CACHE) {
if (delete_mrt_flag == TRUE)
delete_mrtentry_all_kernel_cache(mrt);
else
k_chg_mfc(igmp_socket, mrt->source->address,
mrt->group->group, new_iif, new_real_oifs,
mrt->group->rpaddr);
}
if (old_iif != new_iif) {
if (new_iif == mrt->source->incoming) {
/* For example, if this was (S,G)RPbit with iif toward the RP,
* and now switch to the Shortest Path.
* The setup of MRTF_SPT flag must be
* done by the external calling function (triggered only
* by receiving of a data from the source.)
*/
mrt->flags &= ~MRTF_RP;
/* TODO: XXX: delete? Check again where will be the best
* place to set it.
mrt->flags |= MRTF_SPT;
*/
}
if ((mwc && mwc->incoming == new_iif) ||
(mrp && mrp->incoming == new_iif)) {
/* If the new iif points toward the RP, reset the SPT flag.
* (PIM-SM-spec-10.ps pp. 11, 2.10, last sentence of first
* paragraph. */
/* TODO: XXX: check again! */
mrt->flags &= ~MRTF_SPT;
mrt->flags |= MRTF_RP;
}
}
/* TODO: XXX: if this is (S,G)RPbit entry and the oifs==(*,G)oifs,
* then delete the (S,G) entry?? The same if we have (*,*,RP) ? */
if (delete_mrt_flag == TRUE) {
/* TODO: XXX: the oifs are NULL. Send a Prune message ? */
}
/* TODO: XXX: have the feeling something is missing.... */
return result; /* (S,G) */
}
return result;
}
/* TODO: implement it. Required to allow changing of the physical interfaces
* configuration without need to restart pimd.
*/
int delete_vif_from_mrt(vifi_t vifi __attribute__((unused)))
{
return TRUE;
}
void process_kernel_call(void)
{
struct igmpmsg *igmpctl = (struct igmpmsg *)igmp_recv_buf;
switch (igmpctl->im_msgtype) {
case IGMPMSG_NOCACHE:
process_cache_miss(igmpctl);
break;
case IGMPMSG_WRONGVIF:
process_wrong_iif(igmpctl);
break;
case IGMPMSG_WHOLEPKT:
process_whole_pkt(igmp_recv_buf);
break;
default:
IF_DEBUG(DEBUG_KERN)
logit(LOG_DEBUG, 0, "Unknown IGMP message type from kernel: %d", igmpctl->im_msgtype);
break;
}
}
/*
* TODO: when cache miss, check the iif, because probably ASSERTS
* shoult take place
*/
static void process_cache_miss(struct igmpmsg *igmpctl)
{
uint32_t source, mfc_source;
uint32_t group;
uint32_t rp_addr;
vifi_t iif;
mrtentry_t *mrt;
mrtentry_t *mrp;
/* 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_MRT)
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)) {
mrt = find_route(source, group, MRTF_SG, CREATE);
if (!mrt)
return;
mrt->flags &= ~MRTF_NEW;
/* set reg_vif_num as outgoing interface ONLY if I am not the RP */
if (mrt->group->rpaddr != my_cand_rp_address)
VIFM_SET(reg_vif_num, mrt->joined_oifs);
change_interfaces(mrt,
mrt->incoming,
mrt->joined_oifs,
mrt->pruned_oifs,
mrt->leaves,
mrt->asserted_oifs, 0);
} else {
mrt = find_route(source, group, MRTF_SG | MRTF_WC | MRTF_PMBR, DONT_CREATE);
switch_shortest_path(source, group);
if (!mrt)
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 (mrt->incoming == iif) {
if (!VIFM_ISEMPTY(mrt->oifs)) {
if (mrt->flags & MRTF_SG) {
/* TODO: check that the RPbit is not set? */
/* TODO: XXX: TIMER implem. dependency! */
if (mrt->timer < PIM_DATA_TIMEOUT)
SET_TIMER(mrt->timer, PIM_DATA_TIMEOUT);
if (!(mrt->flags & MRTF_SPT)) {
mrp = mrt->group->grp_route;
if (!mrp)
mrp = mrt->group->active_rp_grp->rp->rpentry->mrtlink;
if (mrp) {
/* Check if the (S,G) iif is different from
* the (*,G) or (*,*,RP) iif */
if ((mrt->incoming != mrp->incoming) ||
(mrt->upstream != mrp->upstream)) {
mrt->flags |= MRTF_SPT;
mrt->flags &= ~MRTF_RP;
}
}
}
}
if (mrt->flags & MRTF_PMBR)
rp_addr = mrt->source->address;
else
rp_addr = mrt->group->rpaddr;
mfc_source = source;
#ifdef KERNEL_MFC_WC_G
if (mrt->flags & (MRTF_WC | MRTF_PMBR))
if (!(mrt->flags & MRTF_MFC_CLONE_SG))
mfc_source = INADDR_ANY_N;
#endif /* KERNEL_MFC_WC_G */
add_kernel_cache(mrt, mfc_source, group, MFC_MOVE_FORCE);
#ifdef SCOPED_ACL
APPLY_SCOPE(group, mrt);
#endif
k_chg_mfc(igmp_socket, mfc_source, group, iif, mrt->oifs, rp_addr);
/* No need for RSRR message, because nothing has changed. */
}
return; /* iif match */
}
/* The iif doesn't match */
if (mrt->flags & MRTF_SG) {
/* Arrived on wrong interface */
if (mrt->flags & MRTF_SPT)
return;
mrp = mrt->group->grp_route;
if (!mrp)
mrp = mrt->group->active_rp_grp->rp->rpentry->mrtlink;
if (mrp) {
/* Forward on (*,G) or (*,*,RP) */
if (mrp->incoming == iif) {
#ifdef KERNEL_MFC_WC_G
if (!(mrp->flags & MRTF_MFC_CLONE_SG))
mfc_source = INADDR_ANY_N;
#endif /* KERNEL_MFC_WC_G */
add_kernel_cache(mrp, mfc_source, group, 0);
#ifdef SCOPED_ACL
/* marian: not sure if we reach here with our scoped traffic? */
APPLY_SCOPE(group, mrt);
#endif
k_chg_mfc(igmp_socket, mfc_source, group, iif,
mrp->oifs, mrt->group->rpaddr);
#ifdef RSRR
rsrr_cache_send(mrp, RSRR_NOTIFICATION_OK);
#endif /* RSRR */
}
}
}
}
/*
* A multicast packet has been received on wrong iif by the kernel.
* Check for a matching entry. If there is (S,G) with reset SPTbit and
* the packet was received on the iif toward the source, this completes
* the switch to the shortest path and triggers (S,G) prune toward the RP
* (unless I am the RP).
* Otherwise, if the packet's iif is in the oiflist of the routing entry,
* trigger an Assert.
*/
static void process_wrong_iif(struct igmpmsg *igmpctl)
{
uint32_t source;
uint32_t group;
vifi_t iif;
mrtentry_t *mrt;
group = igmpctl->im_dst.s_addr;
source = igmpctl->im_src.s_addr;
iif = igmpctl->im_vif;
IF_DEBUG(DEBUG_MRT)
logit(LOG_DEBUG, 0, "Wrong iif: src %s, dst %s, iif %d",
inet_fmt(source, s1, sizeof(s1)), inet_fmt(group, s2, sizeof(s2)), iif);
/* Don't create routing entries for the LAN scoped addresses */
if (ntohl(group) <= INADDR_MAX_LOCAL_GROUP)
return;
/* Ignore if it comes on register vif. register vif is neither SPT iif,
* neither is used to send asserts out.
*/
if (uvifs[iif].uv_flags & VIFF_REGISTER)
return;
mrt = find_route(source, group, MRTF_SG | MRTF_WC | MRTF_PMBR, DONT_CREATE);
if (!mrt)
return;
/*
* TODO: check again!
*/
if (mrt->flags & MRTF_SG) {
if (!(mrt->flags & MRTF_SPT)) {
if (mrt->source->incoming == iif) {
/* Switch to the Shortest Path */
mrt->flags |= MRTF_SPT;
mrt->flags &= ~MRTF_RP;
add_kernel_cache(mrt, source, group, MFC_MOVE_FORCE);
k_chg_mfc(igmp_socket, source, group, iif,
mrt->oifs, mrt->group->rpaddr);
FIRE_TIMER(mrt->jp_timer);
#ifdef RSRR
rsrr_cache_send(mrt, RSRR_NOTIFICATION_OK);
#endif /* RSRR */
return;
}
}
}
/* Trigger an Assert */
if (VIFM_ISSET(iif, mrt->oifs))
send_pim_assert(source, group, iif, mrt);
}
/*
* Receives whole packets from the register vif entries
* in the kernel, and calls the send_pim_register procedure to
* encapsulate the packets and unicasts them to the RP.
*/
static void process_whole_pkt(char *buf)
{
send_pim_register((char *)(buf + sizeof(struct igmpmsg)));
}
mrtentry_t *switch_shortest_path(uint32_t source, uint32_t group)
{
mrtentry_t *mrt;
IF_DEBUG(DEBUG_MRT)
logit(LOG_DEBUG, 0, "Switch shortest path (SPT): src %s, group %s",
inet_fmt(source, s1, sizeof(s1)), inet_fmt(group, s2, sizeof(s2)));
/* TODO: XXX: prepare and send immediately the (S,G) join? */
mrt = find_route(source, group, MRTF_SG, CREATE);
if (mrt) {
if (mrt->flags & MRTF_NEW) {
mrt->flags &= ~MRTF_NEW;
} else if (mrt->flags & MRTF_RP || IN_PIM_SSM_RANGE(group)) {
/* (S,G)RPbit with iif toward RP. Reset to (S,G) with iif
* toward S. Delete the kernel cache (if any), because
* change_interfaces() will reset it with iif toward S
* and no data will arrive from RP before the switch
* really occurs.
* For SSM, (S,G)RPbit entry does not exist but switch to
* SPT must be allowed right away.
*/
mrt->flags &= ~MRTF_RP;
mrt->incoming = mrt->source->incoming;
mrt->upstream = mrt->source->upstream;
delete_mrtentry_all_kernel_cache(mrt);
change_interfaces(mrt,
mrt->incoming,
mrt->joined_oifs,
mrt->pruned_oifs,
mrt->leaves,
mrt->asserted_oifs, 0);
}
SET_TIMER(mrt->timer, PIM_DATA_TIMEOUT);
FIRE_TIMER(mrt->jp_timer);
}
return mrt;
}
/**
* Local Variables:
* version-control: t
* indent-tabs-mode: t
* c-file-style: "ellemtel"
* c-basic-offset: 4
* End:
*/
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>