/* $NetBSD: route.c,v 1.1 1999/07/17 14:06:26 itojun Exp $ */ /* * Copyright (c) 1998 by the University of Oregon. * All rights reserved. * * Permission to use, copy, modify, and distribute this software and * its documentation in source and binary forms for lawful * purposes and without fee is hereby granted, provided * that the above copyright notice appear in all copies and that both * the copyright notice and this permission notice appear in supporting * documentation, and that any documentation, advertising materials, * and other materials related to such distribution and use acknowledge * that the software was developed by the University of Oregon. * The name of the University of Oregon may not be used to endorse or * promote products derived from this software without specific prior * written permission. * * THE UNIVERSITY OF OREGON DOES NOT MAKE ANY REPRESENTATIONS * ABOUT THE SUITABILITY OF THIS SOFTWARE FOR ANY PURPOSE. THIS SOFTWARE IS * PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, TITLE, AND * NON-INFRINGEMENT. * * IN NO EVENT SHALL UO, OR ANY OTHER CONTRIBUTOR BE LIABLE FOR ANY * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES, WHETHER IN CONTRACT, * TORT, OR OTHER FORM OF ACTION, ARISING OUT OF OR IN CONNECTION WITH, * THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Other copyrights might apply to parts of this software and are so * noted when applicable. */ /* * Questions concerning this software should be directed to * Kurt Windisch (kurtw@antc.uoregon.edu) * * KAME Id: route.c,v 1.24 1998/07/06 22:31:14 kurtw Exp */ /* * Part of this program has been derived from PIM sparse-mode pimd. * The pimd program is covered by the license in the accompanying file * named "LICENSE.pimd". * * The pimd program is COPYRIGHT 1998 by University of Southern California. * * Part of this program has been derived from mrouted. * The mrouted program is covered by the license in the accompanying file * named "LICENSE.mrouted". * * The mrouted program is COPYRIGHT 1989 by The Board of Trustees of * Leland Stanford Junior University. * */ #include "defs.h" static u_int16 max_prune_timeout __P((mrtentry_t *)); static void process_cache_miss __P((struct mrt6msg *im)); static void process_wrong_iif __P((struct mrt6msg *im)); u_int32 default_source_preference = DEFAULT_LOCAL_PREF; u_int32 default_source_metric = DEFAULT_LOCAL_METRIC; /* Return the iif for given address */ vifi_t get_iif(address) struct sockaddr_in6 *address; { struct rpfctl rpfc; k_req_incoming(address, &rpfc); if (IN6_IS_ADDR_UNSPECIFIED(&rpfc.rpfneighbor.sin6_addr)) 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_pim6_nbr(source) struct sockaddr_in6 *source; { struct rpfctl rpfc; pim_nbr_entry_t *pim_nbr; struct sockaddr_in6 *next_hop_router_addr; if (local_address(source) != NO_VIF) return (pim_nbr_entry_t *)NULL; k_req_incoming(source, &rpfc); if ((IN6_IS_ADDR_UNSPECIFIED(&rpfc.rpfneighbor.sin6_addr)) || (rpfc.iif == NO_VIF)) return (pim_nbr_entry_t *)NULL; next_hop_router_addr = &rpfc.rpfneighbor; for (pim_nbr = uvifs[rpfc.iif].uv_pim_neighbors; pim_nbr != (pim_nbr_entry_t *)NULL; pim_nbr = pim_nbr->next) if (inet6_equal(&pim_nbr->address, next_hop_router_addr)) return(pim_nbr); return (pim_nbr_entry_t *)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? */ /* PIMDM 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 srctype==PIM_IIF_SOURCE and if the source is directly connected * then the "upstream" is set to NULL. * Note that srctype is a hold-over from the PIM-SM daemon and is unused. */ int set_incoming(srcentry_ptr, srctype) srcentry_t *srcentry_ptr; int srctype; { struct rpfctl rpfc; struct sockaddr_in6 *source = &srcentry_ptr->address; struct sockaddr_in6 *neighbor_addr; register struct uvif *v; register pim_nbr_entry_t *n; /* Preference will be 0 if directly connected */ srcentry_ptr->preference = 0; srcentry_ptr->metric = 0; if ((srcentry_ptr->incoming = local_address(source)) != NO_VIF) { /* The source is a local address */ /* TODO: set the upstream to myself? */ srcentry_ptr->upstream = (pim_nbr_entry_t *)NULL; return (TRUE); } if ((srcentry_ptr->incoming = find_vif_direct(source)) == NO_VIF) { /* TODO: probably need to check the case if the iif is disabled */ /* Use the lastest resource: the kernel unicast routing table */ k_req_incoming(source, &rpfc); if ((rpfc.iif == NO_VIF) || IN6_IS_ADDR_UNSPECIFIED(&rpfc.rpfneighbor.sin6_addr)) { /* couldn't find a route */ IF_DEBUG(DEBUG_PIM_MRT | DEBUG_RPF) log(LOG_DEBUG, 0, "NO ROUTE found for %s", inet6_fmt(&source->sin6_addr)); return(FALSE); } srcentry_ptr->incoming = rpfc.iif; neighbor_addr = &rpfc.rpfneighbor; } else { /* The source is directly connected. */ srcentry_ptr->upstream = (pim_nbr_entry_t *)NULL; return (TRUE); } /* set the preference for sources that aren't directly connected. */ v = &uvifs[srcentry_ptr->incoming]; srcentry_ptr->preference = v->uv_local_pref; srcentry_ptr->metric = v->uv_local_metric; /* * The upstream router must be a (PIM router) neighbor, otherwise we * are in big trouble ;-) */ for (n = v->uv_pim_neighbors; n != NULL; n = n->next) { if (inet6_lessthan(neighbor_addr, &n->address)) continue; if (inet6_equal(neighbor_addr, &n->address)) { /* *The upstream router is found in the list of neighbors. * We are safe! */ srcentry_ptr->upstream = n; IF_DEBUG(DEBUG_RPF) log(LOG_DEBUG, 0, "For src %s, iif is %d, next hop router is %s", inet6_fmt(&source->sin6_addr), srcentry_ptr->incoming, inet6_fmt(&neighbor_addr->sin6_addr)); return(TRUE); } else break; } /* TODO: control the number of messages! */ log(LOG_INFO, 0, "For src %s, iif is %d, next hop router is %s: NOT A PIM ROUTER", inet6_fmt(&source->sin6_addr), srcentry_ptr->incoming, inet6_fmt(&neighbor_addr->sin6_addr)); srcentry_ptr->upstream = (pim_nbr_entry_t *)NULL; return(FALSE); } /* Set the leaves in a new mrtentry */ void set_leaves(mrtentry_ptr) mrtentry_t *mrtentry_ptr; { vifi_t vifi; struct uvif *v; /* Check for a group report on each vif */ for (vifi = 0, v = uvifs; vifi < numvifs; ++vifi, ++v) if(check_multicast_listener(v, &mrtentry_ptr->group->group)) IF_SET(vifi, &mrtentry_ptr->leaves); } /* Handle new receiver * * TODO: XXX: currently `source` is not used. Will be used with IGMPv3 where * we have source-specific Join/Prune. */ void add_leaf(vifi, source, group) vifi_t vifi; struct sockaddr_in6 *source; struct sockaddr_in6 *group; { grpentry_t *grpentry_ptr; mrtentry_t *mrtentry_srcs; if_set new_leaves; int state_change; grpentry_ptr = find_group(group); if (grpentry_ptr == (grpentry_t *)NULL) return; /* walk the source list for the group and add vif to oiflist */ for (mrtentry_srcs = grpentry_ptr->mrtlink; mrtentry_srcs != (mrtentry_t *)NULL; mrtentry_srcs = mrtentry_srcs->grpnext) { /* if applicable, add the vif to the leaves */ if (mrtentry_srcs->incoming == vifi) continue; if(!(IF_ISSET(vifi, &mrtentry_srcs->leaves))) { IF_DEBUG(DEBUG_MRT) log(LOG_DEBUG, 0, "Adding leaf vif %d for src %s group %s", vifi, inet6_fmt(&mrtentry_srcs->source->address.sin6_addr), inet6_fmt(&group->sin6_addr)); IF_COPY(&mrtentry_srcs->leaves, &new_leaves); IF_SET(vifi, &new_leaves); /* Add the leaf */ state_change = change_interfaces(mrtentry_srcs, mrtentry_srcs->incoming, &mrtentry_srcs->pruned_oifs, &new_leaves); /* Handle transition from negative cache */ if(state_change == 1) trigger_join_alert(mrtentry_srcs); } } } /* * TODO: XXX: currently `source` is not used. To be used with IGMPv3 where * we have source-specific joins/prunes. */ void delete_leaf(vifi, source, group) vifi_t vifi; struct sockaddr_in6 *source; struct sockaddr_in6 *group; { grpentry_t *grpentry_ptr; mrtentry_t *mrtentry_srcs; if_set new_leaves; int state_change; /* mrtentry_t *mrtentry_ptr; * mrtentry_t *mrtentry_srcs; * vifbitmap_t new_oifs; * vifbitmap_t old_oifs; * vifbitmap_t new_leaves; */ grpentry_ptr = find_group(group); if (grpentry_ptr == (grpentry_t *)NULL) return; /* walk the source list for the group and delete vif to leaves */ for (mrtentry_srcs = grpentry_ptr->mrtlink; mrtentry_srcs != (mrtentry_t *)NULL; mrtentry_srcs = mrtentry_srcs->grpnext) { /* if applicable, delete the vif from the leaves */ if (mrtentry_srcs->incoming == vifi) continue; if(IF_ISSET(vifi, &mrtentry_srcs->leaves)) { IF_DEBUG(DEBUG_MRT) log(LOG_DEBUG, 0, "Deleting leaf vif %d for src %s, group %s", vifi, inet6_fmt(&mrtentry_srcs->source->address.sin6_addr), inet6_fmt(&group->sin6_addr)); IF_COPY(&mrtentry_srcs->leaves, &new_leaves); IF_CLR(vifi, &new_leaves); /* Remove the leaf */ state_change = change_interfaces(mrtentry_srcs, mrtentry_srcs->incoming, &mrtentry_srcs->pruned_oifs, &new_leaves); /* Handle transition to negative cache */ if(state_change == -1) trigger_prune_alert(mrtentry_srcs); } } } void calc_oifs(mrtentry_ptr, oifs_ptr) mrtentry_t *mrtentry_ptr; if_set *oifs_ptr; { if_set oifs; /* * oifs = * ((nbr_ifs - my_prune) + my_leaves) - my_filters - incoming_interface, * i.e.`leaves` have higher priority than `prunes`, but lower than `filters'. * Asserted oifs (those that lost assert) are handled as pruned oifs. * The incoming interface is always deleted from the oifs */ if (mrtentry_ptr == (mrtentry_t *)NULL) { IF_ZERO(oifs_ptr); return; } IF_COPY(&nbr_mifs, &oifs); IF_CLR_MASK(&oifs, &mrtentry_ptr->pruned_oifs); IF_MERGE(&oifs, &mrtentry_ptr->leaves, &oifs); IF_CLR_MASK(&oifs, &mrtentry_ptr->filter_oifs); IF_CLR(mrtentry_ptr->incoming, &oifs); IF_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_ptr, new_iif, new_pruned_oifs, new_leaves_) mrtentry_t *mrtentry_ptr; vifi_t new_iif; if_set *new_pruned_oifs; if_set *new_leaves_; { if_set old_pruned_oifs; if_set old_leaves; if_set new_leaves; if_set new_real_oifs; /* The result oifs */ if_set old_real_oifs; vifi_t old_iif; int return_value; if (mrtentry_ptr == (mrtentry_t *)NULL) return (0); IF_COPY(new_leaves_, &new_leaves); old_iif = mrtentry_ptr->incoming; IF_COPY(&mrtentry_ptr->leaves, &old_leaves); IF_COPY(&mrtentry_ptr->pruned_oifs, &old_pruned_oifs); IF_COPY(&mrtentry_ptr->oifs, &old_real_oifs); mrtentry_ptr->incoming = new_iif; IF_COPY(new_pruned_oifs, &mrtentry_ptr->pruned_oifs); IF_COPY(&new_leaves, &mrtentry_ptr->leaves); calc_oifs(mrtentry_ptr, &new_real_oifs); if (IF_ISEMPTY(&old_real_oifs)) { if (IF_ISEMPTY(&new_real_oifs)) return_value = 0; else return_value = 1; } else { if (IF_ISEMPTY(&new_real_oifs)) return_value = -1; else return_value = 0; } if ((IF_SAME(&new_real_oifs, &old_real_oifs)) && (new_iif == old_iif)) return 0; /* Nothing to change */ IF_COPY(&new_real_oifs, &mrtentry_ptr->oifs); k_chg_mfc(mld6_socket, &mrtentry_ptr->source->address, &mrtentry_ptr->group->group, new_iif, &new_real_oifs); #ifdef RSRR rsrr_cache_send(mrtentry_ptr, RSRR_NOTIFICATION_OK); #endif /* RSRR */ return (return_value); } /* TODO: implement it. Required to allow changing of the physical interfaces * configuration without need to restart pimd. */ int delete_vif_from_mrt(vifi) vifi_t vifi; { return TRUE; } static u_int16 max_prune_timeout(mrtentry_ptr) mrtentry_t *mrtentry_ptr; { vifi_t vifi; #if 0 /* XXX: I don't understand how the variable works...(jinmei@kame.net) */ u_int16 time_left = 0; #endif u_int16 max_holdtime = 0; for(vifi=0; vifi < numvifs; ++vifi) if(IF_ISSET(vifi, &mrtentry_ptr->pruned_oifs) && mrtentry_ptr->prune_timers[vifi]) /* XXX - too expensive ? */ if(mrtentry_ptr->prune_timers[vifi] > max_holdtime) max_holdtime = mrtentry_ptr->prune_timers[vifi]; #if 0 /* XXX: This is original. But does it have any meaning? */ max_holdtime = time_left; #endif if(max_holdtime == 0) max_holdtime = (u_int16)PIM_JOIN_PRUNE_HOLDTIME; return(max_holdtime); } void process_kernel_call() { register struct mrt6msg *im; /* igmpmsg control struct */ im = (struct mrt6msg *) mld6_recv_buf; switch (im->im6_msgtype) { case MRT6MSG_NOCACHE: process_cache_miss(im); break; case MRT6MSG_WRONGMIF: process_wrong_iif(im); break; default: IF_DEBUG(DEBUG_KERN) log(LOG_DEBUG, 0, "Unknown kernel_call code, %d", im->im6_msgtype); break; } } /* * Protocol actions: * 1. Create (S,G) entry (find_route(CREATE)) * a. set iif and oifs */ static void process_cache_miss(im) struct mrt6msg *im; { static struct sockaddr_in6 source = {sizeof(source), AF_INET6}; static struct sockaddr_in6 group = {sizeof(group), AF_INET6}; mrtentry_t *mrtentry_ptr; /* * 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.sin6_addr = im->im6_dst; source.sin6_addr = im->im6_src; group.sin6_scope_id = inet6_uvif2scopeid(&group, &uvifs[im->im6_mif]); source.sin6_scope_id = inet6_uvif2scopeid(&source, &uvifs[im->im6_mif]); IF_DEBUG(DEBUG_MFC) log(LOG_DEBUG, 0, "Cache miss, src %s, dst %s", inet6_fmt(&source.sin6_addr), inet6_fmt(&group.sin6_addr)); /* Don't create routing entries for the LAN scoped addresses */ if (IN6_IS_ADDR_MC_NODELOCAL(&group.sin6_addr) ||/* sanity? */ IN6_IS_ADDR_MC_LINKLOCAL(&group.sin6_addr)) return; /* Create the (S,G) entry */ mrtentry_ptr = find_route(&source, &group, MRTF_SG, CREATE); if (mrtentry_ptr == (mrtentry_t *)NULL) return; mrtentry_ptr->flags &= ~MRTF_NEW; /* Set oifs */ set_leaves(mrtentry_ptr); calc_oifs(mrtentry_ptr, &(mrtentry_ptr->oifs)); /* Add it to the kernel */ k_chg_mfc(mld6_socket, &source, &group, mrtentry_ptr->incoming, &mrtentry_ptr->oifs); #ifdef RSRR rsrr_cache_send(mrtentry_ptr, RSRR_NOTIFICATION_OK); #endif /* RSRR */ /* No need to call change_interfaces, but check for NULL oiflist */ if(IF_ISEMPTY(&mrtentry_ptr->oifs)) trigger_prune_alert(mrtentry_ptr); } /* * A multicast packet has been received on wrong iif by the kernel. * If the packet was received on a point-to-point interface, rate-limit * prunes. if the packet was received on a LAN interface, rate-limit * asserts. */ static void process_wrong_iif(im) struct mrt6msg *im; { static struct sockaddr_in6 source = {sizeof(source), AF_INET6}; static struct sockaddr_in6 group = {sizeof(group), AF_INET6}; mifi_t mifi; mrtentry_t *mrtentry_ptr; group.sin6_addr = im->im6_dst; source.sin6_addr = im->im6_src; mifi = (mifi_t)im->im6_mif; group.sin6_scope_id = inet6_uvif2scopeid(&group, &uvifs[mifi]); source.sin6_scope_id = inet6_uvif2scopeid(&source, &uvifs[mifi]); /* PIMDM TODO Don't create routing entries for the LAN scoped addresses */ if (IN6_IS_ADDR_MC_NODELOCAL(&group.sin6_addr) ||/* sanity? */ IN6_IS_ADDR_MC_LINKLOCAL(&group.sin6_addr)) return; mrtentry_ptr = find_route(&source, &group, MRTF_SG, DONT_CREATE); if(mrtentry_ptr == (mrtentry_t *)NULL) return; /* Ratelimit prunes or asserts */ #ifdef notyet if(uvifs[mifi].uv_flags & VIFF_POINT_TO_POINT) { /* Wrong vif on P2P interface - rate-limit prunes */ if(mrtentry_ptr->last_prune[mifi] == virtual_time) /* Skip due to rate-limiting */ return; mrtentry_ptr->last_prune[mifi] = virtual_time; if(uvifs[mifi].uv_rmt_addr) send_pim6_jp(mrtentry_ptr, PIM_ACTION_PRUNE, mifi, uvifs[mifi].uv_rmt_addr, max_prune_timeout(mrtentry_ptr)); else log(LOG_WARNING, 0, "Can't send wrongvif prune on p2p %s: no remote address", uvifs[mifi].uv_lcl_addr); } else #endif { /* Wrong vif on LAN interface - rate-limit asserts */ if(mrtentry_ptr->last_assert[mifi] == virtual_time) /* Skip due to rate-limiting */ return; mrtentry_ptr->last_assert[mifi] = virtual_time; /* Send the assert */ send_pim6_assert(&source, &group, mifi, mrtentry_ptr); } } void trigger_prune_alert(mrtentry_ptr) mrtentry_t *mrtentry_ptr; { IF_DEBUG(DEBUG_MRT) log(LOG_DEBUG, 0, "Now negative cache for src %s, grp %s - pruning", inet6_fmt(&mrtentry_ptr->source->address.sin6_addr), inet6_fmt(&mrtentry_ptr->group->group.sin6_addr)); /* Set the entry timer to the max of the prune timers */ SET_TIMER(mrtentry_ptr->timer, max_prune_timeout(mrtentry_ptr)); /* Send a prune */ if(mrtentry_ptr->upstream) send_pim6_jp(mrtentry_ptr, PIM_ACTION_PRUNE, mrtentry_ptr->incoming, &mrtentry_ptr->upstream->address, max_prune_timeout(mrtentry_ptr)); } void trigger_join_alert(mrtentry_ptr) mrtentry_t *mrtentry_ptr; { IF_DEBUG(DEBUG_MRT) log(LOG_DEBUG, 0, "Now forwarding state for src %s, grp %s - grafting", inet6_fmt(&mrtentry_ptr->source->address.sin6_addr), inet6_fmt(&mrtentry_ptr->group->group.sin6_addr)); /* Refresh the entry timer */ SET_TIMER(mrtentry_ptr->timer, PIM_DATA_TIMEOUT); /* Send graft */ send_pim6_graft(mrtentry_ptr); }