/* $NetBSD: prune.c,v 1.18 2009/04/17 16:05:43 lukem Exp $ */ /* * The mrouted program is covered by the license in the accompanying file * named "LICENSE". Use of the mrouted program represents acceptance of * the terms and conditions listed in that file. * * The mrouted program is COPYRIGHT 1989 by The Board of Trustees of * Leland Stanford Junior University. */ #include "defs.h" extern int cache_lifetime; extern int max_prune_lifetime; extern struct rtentry *routing_table; extern int phys_vif; /* * dither cache lifetime to obtain a value between x and 2*x */ #define CACHE_LIFETIME(x) ((x) + (arc4random() % (x))) #define CHK_GS(x, y) { \ switch(x) { \ case 2: \ case 4: \ case 8: \ case 16: \ case 32: \ case 64: \ case 128: \ /*case 256:*/ y = 1; \ break; \ default: y = 0; \ } \ } struct gtable *kernel_table; /* ptr to list of kernel grp entries*/ static struct gtable *kernel_no_route; /* list of grp entries w/o routes */ struct gtable *gtp; /* pointer for kernel rt entries */ unsigned int kroutes; /* current number of cache entries */ /**************************************************************************** Functions that are local to prune.c ****************************************************************************/ static void prun_add_ttls(struct gtable *gt); static int pruning_neighbor(vifi_t vifi, u_int32_t addr); static int can_mtrace(vifi_t vifi, u_int32_t addr); static struct ptable * find_prune_entry(u_int32_t vr, struct ptable *pt); static void expire_prune(vifi_t vifi, struct gtable *gt); static void send_prune(struct gtable *gt); static void send_graft(struct gtable *gt); static void send_graft_ack(u_int32_t src, u_int32_t dst, u_int32_t origin, u_int32_t grp); static void update_kernel(struct gtable *g); static const char * scaletime(u_long t); /* * Updates the ttl values for each vif. */ static void prun_add_ttls(struct gtable *gt) { struct uvif *v; vifi_t vifi; for (vifi = 0, v = uvifs; vifi < numvifs; ++vifi, ++v) { if (VIFM_ISSET(vifi, gt->gt_grpmems)) gt->gt_ttls[vifi] = v->uv_threshold; else gt->gt_ttls[vifi] = 0; } } /* * checks for scoped multicast addresses */ #define GET_SCOPE(gt) { \ vifi_t _i; \ if ((ntohl((gt)->gt_mcastgrp) & 0xff000000) == 0xef000000) \ for (_i = 0; _i < numvifs; _i++) \ if (scoped_addr(_i, (gt)->gt_mcastgrp)) \ VIFM_SET(_i, (gt)->gt_scope); \ } int scoped_addr(vifi_t vifi, u_int32_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; } /* * Determine if mcastgrp has a listener on vifi */ int grplst_mem(vifi_t vifi, u_int32_t mcastgrp) { struct listaddr *g; struct uvif *v; v = &uvifs[vifi]; for (g = v->uv_groups; g != NULL; g = g->al_next) if (mcastgrp == g->al_addr) return 1; return 0; } /* * Finds the group entry with the specified source and netmask. * If netmask is 0, it uses the route's netmask. * * Returns TRUE if found a match, and the global variable gtp is left * pointing to entry before the found entry. * Returns FALSE if no exact match found, gtp is left pointing to before * the entry in question belongs, or is NULL if the it belongs at the * head of the list. */ int find_src_grp(u_int32_t src, u_int32_t mask, u_int32_t grp) { struct gtable *gt; gtp = NULL; gt = kernel_table; while (gt != NULL) { if (grp == gt->gt_mcastgrp && (mask ? (gt->gt_route->rt_origin == src && gt->gt_route->rt_originmask == mask) : ((src & gt->gt_route->rt_originmask) == gt->gt_route->rt_origin))) return TRUE; if (ntohl(grp) > ntohl(gt->gt_mcastgrp) || (grp == gt->gt_mcastgrp && (ntohl(mask) < ntohl(gt->gt_route->rt_originmask) || (mask == gt->gt_route->rt_originmask && (ntohl(src) > ntohl(gt->gt_route->rt_origin)))))) { gtp = gt; gt = gt->gt_gnext; } else break; } return FALSE; } /* * Check if the neighbor supports pruning */ static int pruning_neighbor(vifi_t vifi, u_int32_t addr) { struct listaddr *n = neighbor_info(vifi, addr); int vers; if (n == NULL) return 0; if (n->al_flags & NF_PRUNE) return 1; /* * Versions from 3.0 to 3.4 relied on the version number to identify * that they could handle pruning. */ vers = NBR_VERS(n); return (vers >= 0x0300 && vers <= 0x0304); } /* * Can the neighbor in question handle multicast traceroute? */ static int can_mtrace(vifi_t vifi, u_int32_t addr) { struct listaddr *n = neighbor_info(vifi, addr); int vers; if (n == NULL) return 0; if (n->al_flags & NF_MTRACE) return 1; /* * Versions 3.3 and 3.4 relied on the version number to identify * that they could handle traceroute. */ vers = NBR_VERS(n); return (vers >= 0x0303 && vers <= 0x0304); } /* * Returns the prune entry of the router, or NULL if none exists */ static struct ptable * find_prune_entry(u_int32_t vr, struct ptable *pt) { while (pt) { if (pt->pt_router == vr) return pt; pt = pt->pt_next; } return NULL; } /* * Send a prune message to the dominant router for * this source. * * Record an entry that a prune was sent for this group */ static void send_prune(struct gtable *gt) { struct ptable *pt; char *p; int i; int datalen; u_int32_t src; u_int32_t dst; u_int32_t tmp; /* Don't process any prunes if router is not pruning */ if (pruning == 0) return; /* Can't process a prune if we don't have an associated route */ if (gt->gt_route == NULL) return; /* Don't send a prune to a non-pruning router */ if (!pruning_neighbor(gt->gt_route->rt_parent, gt->gt_route->rt_gateway)) return; /* * sends a prune message to the router upstream. */ src = uvifs[gt->gt_route->rt_parent].uv_lcl_addr; dst = gt->gt_route->rt_gateway; p = send_buf + MIN_IP_HEADER_LEN + IGMP_MINLEN; datalen = 0; /* * determine prune lifetime */ gt->gt_prsent_timer = gt->gt_timer; for (pt = gt->gt_pruntbl; pt; pt = pt->pt_next) if (pt->pt_timer < gt->gt_prsent_timer) gt->gt_prsent_timer = pt->pt_timer; /* * If we have a graft pending, cancel graft retransmission */ gt->gt_grftsnt = 0; for (i = 0; i < 4; i++) *p++ = ((char *)&(gt->gt_route->rt_origin))[i]; for (i = 0; i < 4; i++) *p++ = ((char *)&(gt->gt_mcastgrp))[i]; tmp = htonl(gt->gt_prsent_timer); for (i = 0; i < 4; i++) *p++ = ((char *)&(tmp))[i]; datalen += 12; send_igmp(src, dst, IGMP_DVMRP, DVMRP_PRUNE, htonl(MROUTED_LEVEL), datalen); logit(LOG_DEBUG, 0, "sent prune for (%s %s)/%d on vif %d to %s", inet_fmts(gt->gt_route->rt_origin, gt->gt_route->rt_originmask), inet_fmt(gt->gt_mcastgrp), gt->gt_prsent_timer, gt->gt_route->rt_parent, inet_fmt(gt->gt_route->rt_gateway)); } /* * a prune was sent upstream * so, a graft has to be sent to annul the prune * set up a graft timer so that if an ack is not * heard within that time, another graft request * is sent out. */ static void send_graft(struct gtable *gt) { char *p; int i; int datalen; u_int32_t src; u_int32_t dst; /* Can't send a graft without an associated route */ if (gt->gt_route == NULL) return; src = uvifs[gt->gt_route->rt_parent].uv_lcl_addr; dst = gt->gt_route->rt_gateway; p = send_buf + MIN_IP_HEADER_LEN + IGMP_MINLEN; datalen = 0; for (i = 0; i < 4; i++) *p++ = ((char *)&(gt->gt_route->rt_origin))[i]; for (i = 0; i < 4; i++) *p++ = ((char *)&(gt->gt_mcastgrp))[i]; datalen += 8; if (datalen != 0) { send_igmp(src, dst, IGMP_DVMRP, DVMRP_GRAFT, htonl(MROUTED_LEVEL), datalen); } logit(LOG_DEBUG, 0, "sent graft for (%s %s) to %s on vif %d", inet_fmts(gt->gt_route->rt_origin, gt->gt_route->rt_originmask), inet_fmt(gt->gt_mcastgrp), inet_fmt(gt->gt_route->rt_gateway), gt->gt_route->rt_parent); } /* * Send an ack that a graft was received */ static void send_graft_ack(u_int32_t src, u_int32_t dst, u_int32_t origin, u_int32_t grp) { char *p; int i; int datalen; p = send_buf + MIN_IP_HEADER_LEN + IGMP_MINLEN; datalen = 0; for (i = 0; i < 4; i++) *p++ = ((char *)&(origin))[i]; for (i = 0; i < 4; i++) *p++ = ((char *)&(grp))[i]; datalen += 8; send_igmp(src, dst, IGMP_DVMRP, DVMRP_GRAFT_ACK, htonl(MROUTED_LEVEL), datalen); logit(LOG_DEBUG, 0, "sent graft ack for (%s, %s) to %s", inet_fmt(origin), inet_fmt(grp), inet_fmt(dst)); } /* * Update the kernel cache with all the routes hanging off the group entry */ static void update_kernel(struct gtable *g) { struct stable *st; for (st = g->gt_srctbl; st; st = st->st_next) k_add_rg(st->st_origin, g); } /**************************************************************************** Functions that are used externally ****************************************************************************/ #ifdef SNMP #include #include "snmp.h" /* * Find a specific group entry in the group table */ struct gtable * find_grp(u_long grp) { struct gtable *gt; for (gt = kernel_table; gt; gt = gt->gt_gnext) { if (ntohl(grp) < ntohl(gt->gt_mcastgrp)) break; if (gt->gt_mcastgrp == grp) return gt; } return NULL; } /* * Given a group entry and source, find the corresponding source table * entry */ struct stable * find_grp_src(struct gtable *gt, u_long src) { struct stable *st; u_long grp = gt->gt_mcastgrp; struct gtable *gtcurr; for (gtcurr = gt; gtcurr->gt_mcastgrp == grp; gtcurr = gtcurr->gt_gnext) { for (st = gtcurr->gt_srctbl; st; st = st->st_next) if (st->st_origin == src) return st; } return NULL; } /* * Find next entry > specification * * gtpp: ordered by group * stpp: ordered by source */ int next_grp_src_mask(struct gtable **gtpp, struct stable **stpp, u_long grp, u_long src, u_long mask) { struct gtable *gt, *gbest = NULL; struct stable *st, *sbest = NULL; /* Find first group entry >= grp spec */ (*gtpp) = kernel_table; while ((*gtpp) && ntohl((*gtpp)->gt_mcastgrp) < ntohl(grp)) (*gtpp)=(*gtpp)->gt_gnext; if (!(*gtpp)) return 0; /* no more groups */ for (gt = kernel_table; gt; gt=gt->gt_gnext) { /* Since grps are ordered, we can stop when group changes from gbest */ if (gbest && gbest->gt_mcastgrp != gt->gt_mcastgrp) break; for (st = gt->gt_srctbl; st; st=st->st_next) { /* Among those entries > spec, find "lowest" one */ if (((ntohl(gt->gt_mcastgrp)> ntohl(grp)) || (ntohl(gt->gt_mcastgrp)==ntohl(grp) && ntohl(st->st_origin)> ntohl(src)) || (ntohl(gt->gt_mcastgrp)==ntohl(grp) && ntohl(st->st_origin)==src && 0xFFFFFFFF>ntohl(mask))) && (!gbest || (ntohl(gt->gt_mcastgrp)< ntohl(gbest->gt_mcastgrp)) || (ntohl(gt->gt_mcastgrp)==ntohl(gbest->gt_mcastgrp) && ntohl(st->st_origin)< ntohl(sbest->st_origin)))) { gbest = gt; sbest = st; } } } (*gtpp) = gbest; (*stpp) = sbest; return (*gtpp)!=0; } /* * Ensure that sg contains current information for the given group,source. * This is fetched from the kernel as a unit so that counts for the entry * are consistent, i.e. packet and byte counts for the same entry are * read at the same time. */ void refresh_sg(struct sioc_sg_req *sg, struct gtable *gt, struct stable *st) { static int lastq = -1; if (quantum != lastq || sg->src.s_addr!=st->st_origin || sg->grp.s_addr!=gt->gt_mcastgrp) { lastq = quantum; sg->src.s_addr = st->st_origin; sg->grp.s_addr = gt->gt_mcastgrp; ioctl(igmp_socket, SIOCGETSGCNT, (char *)sg); } } /* * Return pointer to a specific route entry. This must be a separate * function from find_route() which modifies rtp. */ struct rtentry * snmp_find_route(u_long src, u_long mask) { struct rtentry *rt; for (rt = routing_table; rt; rt = rt->rt_next) { if (src == rt->rt_origin && mask == rt->rt_originmask) return rt; } return NULL; } /* * Find next route entry > specification */ int next_route(struct rtentry **rtpp, u_long src, u_long mask) { struct rtentry *rt, *rbest = NULL; /* Among all entries > spec, find "lowest" one in order */ for (rt = routing_table; rt; rt=rt->rt_next) { if ((ntohl(rt->rt_origin) > ntohl(src) || (ntohl(rt->rt_origin) == ntohl(src) && ntohl(rt->rt_originmask) > ntohl(mask))) && (!rbest || (ntohl(rt->rt_origin) < ntohl(rbest->rt_origin)) || (ntohl(rt->rt_origin) == ntohl(rbest->rt_origin) && ntohl(rt->rt_originmask) < ntohl(rbest->rt_originmask)))) rbest = rt; } (*rtpp) = rbest; return (*rtpp)!=0; } /* * Given a routing table entry, and a vifi, find the next vifi/entry * * vifi: vifi at which to start looking */ int next_route_child(struct rtentry **rtpp, u_long src, u_long mask, vifi_t *vifi) { struct rtentry *rt; /* Get (S,M) entry */ if (!((*rtpp) = snmp_find_route(src,mask))) if (!next_route(rtpp, src, mask)) return 0; /* Continue until we get one with a valid next vif */ do { for (; (*rtpp)->rt_children && *vifirt_children)) return 1; *vifi = 0; } while( next_route(rtpp, (*rtpp)->rt_origin, (*rtpp)->rt_originmask) ); return 0; } /* * Given a routing table entry, and a vifi, find the next entry * equal to or greater than those * * vifi: vifi at which to start looking */ int next_child(struct gtable **gtpp, struct stable **stpp, u_long grp, u_long src, u_long mask, vifi_t *vifi) { struct stable *st; /* Get (G,S,M) entry */ if (mask!=0xFFFFFFFF || !((*gtpp) = find_grp(grp)) || !((*stpp) = find_grp_src((*gtpp),src))) if (!next_grp_src_mask(gtpp, stpp, grp, src, mask)) return 0; /* Continue until we get one with a valid next vif */ do { for (; (*gtpp)->gt_route->rt_children && *vifigt_route->rt_children)) return 1; *vifi = 0; } while (next_grp_src_mask(gtpp, stpp, (*gtpp)->gt_mcastgrp, (*stpp)->st_origin, 0xFFFFFFFF) ); return 0; } #endif /* SNMP */ /* * Initialize the kernel table structure */ void init_ktable(void) { kernel_table = NULL; kernel_no_route = NULL; kroutes = 0; } /* * Add a new table entry for (origin, mcastgrp) */ void add_table_entry(u_int32_t origin, u_int32_t mcastgrp) { struct rtentry *r; struct gtable *gt,**gtnp,*prev_gt; struct stable *st,**stnp; vifi_t i; #ifdef DEBUG_MFC md_log(MD_MISS, origin, mcastgrp); #endif r = determine_route(origin); prev_gt = NULL; if (r == NULL) { /* * Look for it on the no_route table; if it is found then * it will be detected as a duplicate below. */ for (gt = kernel_no_route; gt; gt = gt->gt_next) if (mcastgrp == gt->gt_mcastgrp && gt->gt_srctbl && gt->gt_srctbl->st_origin == origin) break; gtnp = &kernel_no_route; } else { gtnp = &r->rt_groups; while ((gt = *gtnp) != NULL) { if (gt->gt_mcastgrp >= mcastgrp) break; gtnp = >->gt_next; prev_gt = gt; } } if (gt == NULL || gt->gt_mcastgrp != mcastgrp) { gt = (struct gtable *)malloc(sizeof(struct gtable)); if (gt == NULL) { logit(LOG_ERR, 0, "ran out of memory"); return; } gt->gt_mcastgrp = mcastgrp; gt->gt_timer = CACHE_LIFETIME(cache_lifetime); time(>->gt_ctime); gt->gt_grpmems = 0; gt->gt_scope = 0; gt->gt_prsent_timer = 0; gt->gt_grftsnt = 0; gt->gt_srctbl = NULL; gt->gt_pruntbl = NULL; gt->gt_route = r; #ifdef RSRR gt->gt_rsrr_cache = NULL; #endif if (r != NULL) { /* obtain the multicast group membership list */ for (i = 0; i < numvifs; i++) { if (VIFM_ISSET(i, r->rt_children) && !(VIFM_ISSET(i, r->rt_leaves))) VIFM_SET(i, gt->gt_grpmems); if (VIFM_ISSET(i, r->rt_leaves) && grplst_mem(i, mcastgrp)) VIFM_SET(i, gt->gt_grpmems); } GET_SCOPE(gt); if (VIFM_ISSET(r->rt_parent, gt->gt_scope)) gt->gt_scope = -1; gt->gt_grpmems &= ~gt->gt_scope; } else { gt->gt_scope = -1; gt->gt_grpmems = 0; } /* update ttls */ prun_add_ttls(gt); gt->gt_next = *gtnp; *gtnp = gt; if (gt->gt_next) gt->gt_next->gt_prev = gt; gt->gt_prev = prev_gt; if (r) { if (find_src_grp(r->rt_origin, r->rt_originmask, gt->gt_mcastgrp)) { struct gtable *g; g = gtp ? gtp->gt_gnext : kernel_table; logit(LOG_WARNING, 0, "Entry for (%s %s) (rt:%p) exists (rt:%p)", inet_fmts(r->rt_origin, r->rt_originmask), inet_fmt(g->gt_mcastgrp), r, g->gt_route); } else { if (gtp) { gt->gt_gnext = gtp->gt_gnext; gt->gt_gprev = gtp; gtp->gt_gnext = gt; } else { gt->gt_gnext = kernel_table; gt->gt_gprev = NULL; kernel_table = gt; } if (gt->gt_gnext) gt->gt_gnext->gt_gprev = gt; } } else { gt->gt_gnext = gt->gt_gprev = NULL; } } stnp = >->gt_srctbl; while ((st = *stnp) != NULL) { if (ntohl(st->st_origin) >= ntohl(origin)) break; stnp = &st->st_next; } if (st == NULL || st->st_origin != origin) { st = (struct stable *)malloc(sizeof(struct stable)); if (st == NULL) logit(LOG_ERR, 0, "ran out of memory"); st->st_origin = origin; st->st_pktcnt = 0; st->st_next = *stnp; *stnp = st; } else { #ifdef DEBUG_MFC md_log(MD_DUPE, origin, mcastgrp); #endif logit(LOG_WARNING, 0, "kernel entry already exists for (%s %s)", inet_fmt(origin), inet_fmt(mcastgrp)); /* XXX Doing this should cause no harm, and may ensure * kernel<>mrouted synchronization */ k_add_rg(origin, gt); return; } kroutes++; k_add_rg(origin, gt); logit(LOG_DEBUG, 0, "add cache entry (%s %s) gm:%x, parent-vif:%d", inet_fmt(origin), inet_fmt(mcastgrp), gt->gt_grpmems, r ? r->rt_parent : -1); /* If there are no leaf vifs * which have this group, then * mark this src-grp as a prune candidate. */ if (!gt->gt_prsent_timer && !gt->gt_grpmems && r && r->rt_gateway) send_prune(gt); } /* * An mrouter has gone down and come up on an interface * Forward on that interface immediately */ void reset_neighbor_state(vifi_t vifi, u_int32_t addr) { struct rtentry *r; struct gtable *g; struct ptable *pt, **ptnp; struct stable *st; for (g = kernel_table; g; g = g->gt_gnext) { r = g->gt_route; /* * If neighbor was the parent, remove the prune sent state * and all of the source cache info so that prunes get * regenerated. */ if (vifi == r->rt_parent) { if (addr == r->rt_gateway) { logit(LOG_DEBUG, 0, "reset_neighbor_state parent reset (%s %s)", inet_fmts(r->rt_origin, r->rt_originmask), inet_fmt(g->gt_mcastgrp)); g->gt_prsent_timer = 0; g->gt_grftsnt = 0; while ((st = g->gt_srctbl) != NULL) { g->gt_srctbl = st->st_next; k_del_rg(st->st_origin, g); kroutes--; free(st); } } } else { /* * Neighbor was not the parent, send grafts to join the groups */ if (g->gt_prsent_timer) { g->gt_grftsnt = 1; send_graft(g); g->gt_prsent_timer = 0; } /* * Remove any prunes that this router has sent us. */ ptnp = &g->gt_pruntbl; while ((pt = *ptnp) != NULL) { if (pt->pt_vifi == vifi && pt->pt_router == addr) { *ptnp = pt->pt_next; free(pt); } else ptnp = &pt->pt_next; } /* * And see if we want to forward again. */ if (!VIFM_ISSET(vifi, g->gt_grpmems)) { if (VIFM_ISSET(vifi, r->rt_children) && !(VIFM_ISSET(vifi, r->rt_leaves))) VIFM_SET(vifi, g->gt_grpmems); if (VIFM_ISSET(vifi, r->rt_leaves) && grplst_mem(vifi, g->gt_mcastgrp)) VIFM_SET(vifi, g->gt_grpmems); g->gt_grpmems &= ~g->gt_scope; prun_add_ttls(g); /* Update kernel state */ update_kernel(g); #ifdef RSRR /* Send route change notification to reservation protocol. */ rsrr_cache_send(g,1); #endif /* RSRR */ logit(LOG_DEBUG, 0, "reset member state (%s %s) gm:%x", inet_fmts(r->rt_origin, r->rt_originmask), inet_fmt(g->gt_mcastgrp), g->gt_grpmems); } } } } /* * Delete table entry from the kernel * del_flag determines how many entries to delete */ void del_table_entry(struct rtentry *r, u_int32_t mcastgrp, u_int del_flag) { struct gtable *g, *prev_g; struct stable *st, *prev_st; struct ptable *pt, *prev_pt; if (del_flag == DEL_ALL_ROUTES) { g = r->rt_groups; while (g) { logit(LOG_DEBUG, 0, "del_table_entry deleting (%s %s)", inet_fmts(r->rt_origin, r->rt_originmask), inet_fmt(g->gt_mcastgrp)); st = g->gt_srctbl; while (st) { if (k_del_rg(st->st_origin, g) < 0) { logit(LOG_WARNING, errno, "del_table_entry trying to delete (%s, %s)", inet_fmt(st->st_origin), inet_fmt(g->gt_mcastgrp)); } kroutes--; prev_st = st; st = st->st_next; free(prev_st); } g->gt_srctbl = NULL; pt = g->gt_pruntbl; while (pt) { prev_pt = pt; pt = pt->pt_next; free(prev_pt); } g->gt_pruntbl = NULL; if (g->gt_gnext) g->gt_gnext->gt_gprev = g->gt_gprev; if (g->gt_gprev) g->gt_gprev->gt_gnext = g->gt_gnext; else kernel_table = g->gt_gnext; #ifdef RSRR /* Send route change notification to reservation protocol. */ rsrr_cache_send(g,0); rsrr_cache_clean(g); #endif /* RSRR */ prev_g = g; g = g->gt_next; free(prev_g); } r->rt_groups = NULL; } /* * Dummy routine - someday this may be needed, so it is just there */ if (del_flag == DEL_RTE_GROUP) { prev_g = (struct gtable *)&r->rt_groups; for (g = r->rt_groups; g; g = g->gt_next) { if (g->gt_mcastgrp == mcastgrp) { logit(LOG_DEBUG, 0, "del_table_entry deleting (%s %s)", inet_fmts(r->rt_origin, r->rt_originmask), inet_fmt(g->gt_mcastgrp)); st = g->gt_srctbl; while (st) { if (k_del_rg(st->st_origin, g) < 0) { logit(LOG_WARNING, errno, "del_table_entry trying to delete (%s, %s)", inet_fmt(st->st_origin), inet_fmt(g->gt_mcastgrp)); } kroutes--; prev_st = st; st = st->st_next; free(prev_st); } g->gt_srctbl = NULL; pt = g->gt_pruntbl; while (pt) { prev_pt = pt; pt = pt->pt_next; free(prev_pt); } g->gt_pruntbl = NULL; if (g->gt_gnext) g->gt_gnext->gt_gprev = g->gt_gprev; if (g->gt_gprev) g->gt_gprev->gt_gnext = g->gt_gnext; else kernel_table = g->gt_gnext; if (prev_g != (struct gtable *)&r->rt_groups) g->gt_next->gt_prev = prev_g; else g->gt_next->gt_prev = NULL; prev_g->gt_next = g->gt_next; #ifdef RSRR /* Send route change notification to reservation protocol. */ rsrr_cache_send(g,0); rsrr_cache_clean(g); #endif /* RSRR */ free(g); g = prev_g; } else { prev_g = g; } } } } /* * update kernel table entry when a route entry changes */ void update_table_entry(struct rtentry *r) { struct gtable *g; struct ptable *pt, *prev_pt; vifi_t i; for (g = r->rt_groups; g; g = g->gt_next) { pt = g->gt_pruntbl; while (pt) { prev_pt = pt->pt_next; free(pt); pt = prev_pt; } g->gt_pruntbl = NULL; g->gt_grpmems = 0; /* obtain the multicast group membership list */ for (i = 0; i < numvifs; i++) { if (VIFM_ISSET(i, r->rt_children) && !(VIFM_ISSET(i, r->rt_leaves))) VIFM_SET(i, g->gt_grpmems); if (VIFM_ISSET(i, r->rt_leaves) && grplst_mem(i, g->gt_mcastgrp)) VIFM_SET(i, g->gt_grpmems); } if (VIFM_ISSET(r->rt_parent, g->gt_scope)) g->gt_scope = -1; g->gt_grpmems &= ~g->gt_scope; logit(LOG_DEBUG, 0, "updating cache entries (%s %s) gm:%x", inet_fmts(r->rt_origin, r->rt_originmask), inet_fmt(g->gt_mcastgrp), g->gt_grpmems); if (g->gt_grpmems && g->gt_prsent_timer) { g->gt_grftsnt = 1; send_graft(g); g->gt_prsent_timer = 0; } /* update ttls and add entry into kernel */ prun_add_ttls(g); update_kernel(g); #ifdef RSRR /* Send route change notification to reservation protocol. */ rsrr_cache_send(g,1); #endif /* RSRR */ /* Check if we want to prune this group */ if (!g->gt_prsent_timer && g->gt_grpmems == 0 && r->rt_gateway) { g->gt_timer = CACHE_LIFETIME(cache_lifetime); send_prune(g); } } } /* * set the forwarding flag for all mcastgrps on this vifi */ void update_lclgrp(vifi_t vifi, u_int32_t mcastgrp) { struct rtentry *r; struct gtable *g; logit(LOG_DEBUG, 0, "group %s joined on vif %d", inet_fmt(mcastgrp), vifi); for (g = kernel_table; g; g = g->gt_gnext) { if (ntohl(mcastgrp) < ntohl(g->gt_mcastgrp)) break; r = g->gt_route; if (g->gt_mcastgrp == mcastgrp && VIFM_ISSET(vifi, r->rt_children)) { VIFM_SET(vifi, g->gt_grpmems); g->gt_grpmems &= ~g->gt_scope; if (g->gt_grpmems == 0) continue; prun_add_ttls(g); logit(LOG_DEBUG, 0, "update lclgrp (%s %s) gm:%x", inet_fmts(r->rt_origin, r->rt_originmask), inet_fmt(g->gt_mcastgrp), g->gt_grpmems); update_kernel(g); #ifdef RSRR /* Send route change notification to reservation protocol. */ rsrr_cache_send(g,1); #endif /* RSRR */ } } } /* * reset forwarding flag for all mcastgrps on this vifi */ void delete_lclgrp(vifi_t vifi, u_int32_t mcastgrp) { struct rtentry *r; struct gtable *g; logit(LOG_DEBUG, 0, "group %s left on vif %d", inet_fmt(mcastgrp), vifi); for (g = kernel_table; g; g = g->gt_gnext) { if (ntohl(mcastgrp) < ntohl(g->gt_mcastgrp)) break; if (g->gt_mcastgrp == mcastgrp) { int stop_sending = 1; r = g->gt_route; /* * If this is not a leaf, then we have router neighbors on this * vif. Only turn off forwarding if they have all pruned. */ if (!VIFM_ISSET(vifi, r->rt_leaves)) { struct listaddr *vr; for (vr = uvifs[vifi].uv_neighbors; vr; vr = vr->al_next) if (find_prune_entry(vr->al_addr, g->gt_pruntbl) == NULL) { stop_sending = 0; break; } } if (stop_sending) { VIFM_CLR(vifi, g->gt_grpmems); logit(LOG_DEBUG, 0, "delete lclgrp (%s %s) gm:%x", inet_fmts(r->rt_origin, r->rt_originmask), inet_fmt(g->gt_mcastgrp), g->gt_grpmems); prun_add_ttls(g); update_kernel(g); #ifdef RSRR /* Send route change notification to reservation protocol. */ rsrr_cache_send(g,1); #endif /* RSRR */ /* * If there are no more members of this particular group, * send prune upstream */ if (!g->gt_prsent_timer && g->gt_grpmems == 0 && r->rt_gateway) send_prune(g); } } } } /* * Takes the prune message received and then strips it to * determine the (src, grp) pair to be pruned. * * Adds the router to the (src, grp) entry then. * * Determines if further packets have to be sent down that vif * * Determines if a corresponding prune message has to be generated */ void accept_prune(u_int32_t src, u_int32_t dst, char *p, int datalen) { u_int32_t prun_src; u_int32_t prun_grp; u_int32_t prun_tmr; vifi_t vifi; int i; int stop_sending; struct rtentry *r; struct gtable *g; struct ptable *pt; struct listaddr *vr; /* Don't process any prunes if router is not pruning */ if (pruning == 0) return; if ((vifi = find_vif(src, dst)) == NO_VIF) { logit(LOG_INFO, 0, "ignoring prune report from non-neighbor %s", inet_fmt(src)); return; } /* Check if enough data is present */ if (datalen < 12) { logit(LOG_WARNING, 0, "non-decipherable prune from %s", inet_fmt(src)); return; } for (i = 0; i< 4; i++) ((char *)&prun_src)[i] = *p++; for (i = 0; i< 4; i++) ((char *)&prun_grp)[i] = *p++; for (i = 0; i< 4; i++) ((char *)&prun_tmr)[i] = *p++; prun_tmr = ntohl(prun_tmr); logit(LOG_DEBUG, 0, "%s on vif %d prunes (%s %s)/%d", inet_fmt(src), vifi, inet_fmt(prun_src), inet_fmt(prun_grp), prun_tmr); /* * Find the subnet for the prune */ if (find_src_grp(prun_src, 0, prun_grp)) { g = gtp ? gtp->gt_gnext : kernel_table; r = g->gt_route; if (!VIFM_ISSET(vifi, r->rt_children)) { logit(LOG_WARNING, 0, "prune received from non-child %s for (%s %s)", inet_fmt(src), inet_fmt(prun_src), inet_fmt(prun_grp)); return; } if (VIFM_ISSET(vifi, g->gt_scope)) { logit(LOG_WARNING, 0, "prune received from %s on scoped grp (%s %s)", inet_fmt(src), inet_fmt(prun_src), inet_fmt(prun_grp)); return; } if ((pt = find_prune_entry(src, g->gt_pruntbl)) != NULL) { /* * If it's about to expire, then it's only still around because * of timer granularity, so don't warn about it. */ if (pt->pt_timer > 10) { logit(LOG_WARNING, 0, "%s %d from %s for (%s %s)/%d %s %d %s %x", "duplicate prune received on vif", vifi, inet_fmt(src), inet_fmt(prun_src), inet_fmt(prun_grp), prun_tmr, "old timer:", pt->pt_timer, "cur gm:", g->gt_grpmems); } pt->pt_timer = prun_tmr; } else { /* allocate space for the prune structure */ pt = (struct ptable *)(malloc(sizeof(struct ptable))); if (pt == NULL) { logit(LOG_ERR, 0, "pt: ran out of memory"); return; } pt->pt_vifi = vifi; pt->pt_router = src; pt->pt_timer = prun_tmr; pt->pt_next = g->gt_pruntbl; g->gt_pruntbl = pt; } /* Refresh the group's lifetime */ g->gt_timer = CACHE_LIFETIME(cache_lifetime); if ((u_int32_t)g->gt_timer < prun_tmr) g->gt_timer = prun_tmr; /* * check if any more packets need to be sent on the * vif which sent this message */ stop_sending = 1; for (vr = uvifs[vifi].uv_neighbors; vr; vr = vr->al_next) if (find_prune_entry(vr->al_addr, g->gt_pruntbl) == NULL) { stop_sending = 0; break; } if (stop_sending && !grplst_mem(vifi, prun_grp)) { VIFM_CLR(vifi, g->gt_grpmems); logit(LOG_DEBUG, 0, "prune (%s %s), stop sending on vif %d, gm:%x", inet_fmts(r->rt_origin, r->rt_originmask), inet_fmt(g->gt_mcastgrp), vifi, g->gt_grpmems); prun_add_ttls(g); update_kernel(g); #ifdef RSRR /* Send route change notification to reservation protocol. */ rsrr_cache_send(g,1); #endif /* RSRR */ } /* * check if all the child routers have expressed no interest * in this group and if this group does not exist in the * interface * Send a prune message then upstream */ if (!g->gt_prsent_timer && g->gt_grpmems == 0 && r->rt_gateway) { send_prune(g); } } else { /* * There is no kernel entry for this group. Therefore, we can * simply ignore the prune, as we are not forwarding this traffic * downstream. */ logit(LOG_DEBUG, 0, "%s (%s %s)/%d from %s", "prune message received with no kernel entry for", inet_fmt(prun_src), inet_fmt(prun_grp), prun_tmr, inet_fmt(src)); return; } } /* * Checks if this mcastgrp is present in the kernel table * If so and if a prune was sent, it sends a graft upwards */ void chkgrp_graft(vifi_t vifi, u_int32_t mcastgrp) { struct rtentry *r; struct gtable *g; for (g = kernel_table; g; g = g->gt_gnext) { if (ntohl(mcastgrp) < ntohl(g->gt_mcastgrp)) break; r = g->gt_route; if (g->gt_mcastgrp == mcastgrp && VIFM_ISSET(vifi, r->rt_children)) if (g->gt_prsent_timer) { VIFM_SET(vifi, g->gt_grpmems); /* * If the vif that was joined was a scoped vif, * ignore it ; don't graft back */ g->gt_grpmems &= ~g->gt_scope; if (g->gt_grpmems == 0) continue; /* set the flag for graft retransmission */ g->gt_grftsnt = 1; /* send graft upwards */ send_graft(g); /* reset the prune timer and update cache timer*/ g->gt_prsent_timer = 0; g->gt_timer = max_prune_lifetime; logit(LOG_DEBUG, 0, "chkgrp graft (%s %s) gm:%x", inet_fmts(r->rt_origin, r->rt_originmask), inet_fmt(g->gt_mcastgrp), g->gt_grpmems); prun_add_ttls(g); update_kernel(g); #ifdef RSRR /* Send route change notification to reservation protocol. */ rsrr_cache_send(g,1); #endif /* RSRR */ } } } /* determine the multicast group and src * * if it does, then determine if a prune was sent * upstream. * if prune sent upstream, send graft upstream and send * ack downstream. * * if no prune sent upstream, change the forwarding bit * for this interface and send ack downstream. * * if no entry exists for this group send ack downstream. */ void accept_graft(u_int32_t src, u_int32_t dst, char *p, int datalen) { vifi_t vifi; u_int32_t graft_src; u_int32_t graft_grp; int i; struct rtentry *r; struct gtable *g; struct ptable *pt, **ptnp; if ((vifi = find_vif(src, dst)) == NO_VIF) { logit(LOG_INFO, 0, "ignoring graft from non-neighbor %s", inet_fmt(src)); return; } if (datalen < 8) { logit(LOG_WARNING, 0, "received non-decipherable graft from %s", inet_fmt(src)); return; } for (i = 0; i< 4; i++) ((char *)&graft_src)[i] = *p++; for (i = 0; i< 4; i++) ((char *)&graft_grp)[i] = *p++; logit(LOG_DEBUG, 0, "%s on vif %d grafts (%s %s)", inet_fmt(src), vifi, inet_fmt(graft_src), inet_fmt(graft_grp)); /* * Find the subnet for the graft */ if (find_src_grp(graft_src, 0, graft_grp)) { g = gtp ? gtp->gt_gnext : kernel_table; r = g->gt_route; if (VIFM_ISSET(vifi, g->gt_scope)) { logit(LOG_WARNING, 0, "graft received from %s on scoped grp (%s %s)", inet_fmt(src), inet_fmt(graft_src), inet_fmt(graft_grp)); return; } ptnp = &g->gt_pruntbl; while ((pt = *ptnp) != NULL) { if ((pt->pt_vifi == vifi) && (pt->pt_router == src)) { *ptnp = pt->pt_next; free(pt); VIFM_SET(vifi, g->gt_grpmems); logit(LOG_DEBUG, 0, "accept graft (%s %s) gm:%x", inet_fmts(r->rt_origin, r->rt_originmask), inet_fmt(g->gt_mcastgrp), g->gt_grpmems); prun_add_ttls(g); update_kernel(g); #ifdef RSRR /* Send route change notification to reservation protocol. */ rsrr_cache_send(g,1); #endif /* RSRR */ break; } else { ptnp = &pt->pt_next; } } /* send ack downstream */ send_graft_ack(dst, src, graft_src, graft_grp); g->gt_timer = max_prune_lifetime; if (g->gt_prsent_timer) { /* set the flag for graft retransmission */ g->gt_grftsnt = 1; /* send graft upwards */ send_graft(g); /* reset the prune sent timer */ g->gt_prsent_timer = 0; } } else { /* * We have no state for the source and group in question. * We can simply acknowledge the graft, since we know * that we have no prune state, and grafts are requests * to remove prune state. */ send_graft_ack(dst, src, graft_src, graft_grp); logit(LOG_DEBUG, 0, "%s (%s %s) from %s", "graft received with no kernel entry for", inet_fmt(graft_src), inet_fmt(graft_grp), inet_fmt(src)); return; } } /* * find out which group is involved first of all * then determine if a graft was sent. * if no graft sent, ignore the message * if graft was sent and the ack is from the right * source, remove the graft timer so that we don't * have send a graft again */ void accept_g_ack(u_int32_t src, u_int32_t dst, char *p, int datalen) { struct gtable *g; vifi_t vifi; u_int32_t grft_src; u_int32_t grft_grp; int i; if ((vifi = find_vif(src, dst)) == NO_VIF) { logit(LOG_INFO, 0, "ignoring graft ack from non-neighbor %s", inet_fmt(src)); return; } if (datalen < 0 || datalen > 8) { logit(LOG_WARNING, 0, "received non-decipherable graft ack from %s", inet_fmt(src)); return; } for (i = 0; i< 4; i++) ((char *)&grft_src)[i] = *p++; for (i = 0; i< 4; i++) ((char *)&grft_grp)[i] = *p++; logit(LOG_DEBUG, 0, "%s on vif %d acks graft (%s, %s)", inet_fmt(src), vifi, inet_fmt(grft_src), inet_fmt(grft_grp)); /* * Find the subnet for the graft ack */ if (find_src_grp(grft_src, 0, grft_grp)) { g = gtp ? gtp->gt_gnext : kernel_table; g->gt_grftsnt = 0; } else { logit(LOG_WARNING, 0, "%s (%s, %s) from %s", "rcvd graft ack with no kernel entry for", inet_fmt(grft_src), inet_fmt(grft_grp), inet_fmt(src)); return; } } /* * free all prune entries and kernel routes * normally, this should inform the kernel that all of its routes * are going away, but this is only called by restart(), which is * about to call MRT_DONE which does that anyway. */ void free_all_prunes(void) { struct rtentry *r; struct gtable *g, *prev_g; struct stable *s, *prev_s; struct ptable *p, *prev_p; for (r = routing_table; r; r = r->rt_next) { g = r->rt_groups; while (g) { s = g->gt_srctbl; while (s) { prev_s = s; s = s->st_next; free(prev_s); } p = g->gt_pruntbl; while (p) { prev_p = p; p = p->pt_next; free(prev_p); } prev_g = g; g = g->gt_next; free(prev_g); } r->rt_groups = NULL; } kernel_table = NULL; g = kernel_no_route; while (g) { if (g->gt_srctbl) free(g->gt_srctbl); prev_g = g; g = g->gt_next; free(prev_g); } kernel_no_route = NULL; } /* * When a new route is created, search * a) The less-specific part of the routing table * b) The route-less kernel table * for sources that the new route might want to handle. * * "Inheriting" these sources might be cleanest, but simply deleting * them is easier, and letting the kernel re-request them. */ void steal_sources(struct rtentry *rt) { struct rtentry *rp; struct gtable *gt, **gtnp; struct stable *st, **stnp; for (rp = rt->rt_next; rp; rp = rp->rt_next) { if ((rt->rt_origin & rp->rt_originmask) == rp->rt_origin) { logit(LOG_DEBUG, 0, "Route for %s stealing sources from %s", inet_fmts(rt->rt_origin, rt->rt_originmask), inet_fmts(rp->rt_origin, rp->rt_originmask)); for (gt = rp->rt_groups; gt; gt = gt->gt_next) { stnp = >->gt_srctbl; while ((st = *stnp) != NULL) { if ((st->st_origin & rt->rt_originmask) == rt->rt_origin) { logit(LOG_DEBUG, 0, "%s stealing (%s %s) from %s", inet_fmts(rt->rt_origin, rt->rt_originmask), inet_fmt(st->st_origin), inet_fmt(gt->gt_mcastgrp), inet_fmts(rp->rt_origin, rp->rt_originmask)); if (k_del_rg(st->st_origin, gt) < 0) { logit(LOG_WARNING, errno, "%s (%s, %s)", "steal_sources trying to delete", inet_fmt(st->st_origin), inet_fmt(gt->gt_mcastgrp)); } *stnp = st->st_next; kroutes--; free(st); } else { stnp = &st->st_next; } } } } } gtnp = &kernel_no_route; while ((gt = *gtnp) != NULL) { if (gt->gt_srctbl && ((gt->gt_srctbl->st_origin & rt->rt_originmask) == rt->rt_origin)) { logit(LOG_DEBUG, 0, "%s stealing (%s %s) from %s", inet_fmts(rt->rt_origin, rt->rt_originmask), inet_fmt(gt->gt_srctbl->st_origin), inet_fmt(gt->gt_mcastgrp), "no_route table"); if (k_del_rg(gt->gt_srctbl->st_origin, gt) < 0) { logit(LOG_WARNING, errno, "%s (%s %s)", "steal_sources trying to delete", inet_fmt(gt->gt_srctbl->st_origin), inet_fmt(gt->gt_mcastgrp)); } kroutes--; free(gt->gt_srctbl); *gtnp = gt->gt_next; if (gt->gt_next) gt->gt_next->gt_prev = gt->gt_prev; free(gt); } else { gtnp = >->gt_next; } } } /* * Advance the timers on all the cache entries. * If there are any entries whose timers have expired, * remove these entries from the kernel cache. */ void age_table_entry(void) { struct rtentry *r; struct gtable *gt, **gtnptr; struct stable *st, **stnp; struct ptable *pt, **ptnp; struct sioc_sg_req sg_req; logit(LOG_DEBUG, 0, "ageing entries"); gtnptr = &kernel_table; while ((gt = *gtnptr) != NULL) { r = gt->gt_route; /* advance the timer for the kernel entry */ gt->gt_timer -= ROUTE_MAX_REPORT_DELAY; /* decrement prune timer if need be */ if (gt->gt_prsent_timer > 0) { gt->gt_prsent_timer -= ROUTE_MAX_REPORT_DELAY; if (gt->gt_prsent_timer <= 0) { logit(LOG_DEBUG, 0, "upstream prune tmo (%s %s)", inet_fmts(r->rt_origin, r->rt_originmask), inet_fmt(gt->gt_mcastgrp)); gt->gt_prsent_timer = -1; } } /* retransmit graft if graft sent flag is still set */ if (gt->gt_grftsnt) { int y; CHK_GS(gt->gt_grftsnt++, y); if (y) send_graft(gt); } /* * Age prunes * * If a prune expires, forward again on that vif. */ ptnp = >->gt_pruntbl; while ((pt = *ptnp) != NULL) { if ((pt->pt_timer -= ROUTE_MAX_REPORT_DELAY) <= 0) { logit(LOG_DEBUG, 0, "expire prune (%s %s) from %s on vif %d", inet_fmts(r->rt_origin, r->rt_originmask), inet_fmt(gt->gt_mcastgrp), inet_fmt(pt->pt_router), pt->pt_vifi); expire_prune(pt->pt_vifi, gt); /* remove the router's prune entry and await new one */ *ptnp = pt->pt_next; free(pt); } else { ptnp = &pt->pt_next; } } /* * If the cache entry has expired, delete source table entries for * silent sources. If there are no source entries left, and there * are no downstream prunes, then the entry is deleted. * Otherwise, the cache entry's timer is refreshed. */ if (gt->gt_timer <= 0) { /* Check for traffic before deleting source entries */ sg_req.grp.s_addr = gt->gt_mcastgrp; stnp = >->gt_srctbl; while ((st = *stnp) != NULL) { sg_req.src.s_addr = st->st_origin; if (ioctl(igmp_socket, SIOCGETSGCNT, (char *)&sg_req) < 0) { logit(LOG_WARNING, errno, "%s (%s %s)", "age_table_entry: SIOCGETSGCNT failing for", inet_fmt(st->st_origin), inet_fmt(gt->gt_mcastgrp)); /* Make sure it gets deleted below */ sg_req.pktcnt = st->st_pktcnt; } if (sg_req.pktcnt == st->st_pktcnt) { *stnp = st->st_next; logit(LOG_DEBUG, 0, "age_table_entry deleting (%s %s)", inet_fmt(st->st_origin), inet_fmt(gt->gt_mcastgrp)); if (k_del_rg(st->st_origin, gt) < 0) { logit(LOG_WARNING, errno, "age_table_entry trying to delete (%s %s)", inet_fmt(st->st_origin), inet_fmt(gt->gt_mcastgrp)); } kroutes--; free(st); } else { st->st_pktcnt = sg_req.pktcnt; stnp = &st->st_next; } } /* * Retain the group entry if we have downstream prunes or if * there is at least one source in the list that still has * traffic, or if our upstream prune timer is running. */ if (gt->gt_pruntbl != NULL || gt->gt_srctbl != NULL || gt->gt_prsent_timer > 0) { gt->gt_timer = CACHE_LIFETIME(cache_lifetime); if (gt->gt_prsent_timer == -1) { if (gt->gt_grpmems == 0) send_prune(gt); else gt->gt_prsent_timer = 0; } gtnptr = >->gt_gnext; continue; } logit(LOG_DEBUG, 0, "timeout cache entry (%s, %s)", inet_fmts(r->rt_origin, r->rt_originmask), inet_fmt(gt->gt_mcastgrp)); if (gt->gt_prev) gt->gt_prev->gt_next = gt->gt_next; else gt->gt_route->rt_groups = gt->gt_next; if (gt->gt_next) gt->gt_next->gt_prev = gt->gt_prev; if (gt->gt_gprev) { gt->gt_gprev->gt_gnext = gt->gt_gnext; gtnptr = >->gt_gprev->gt_gnext; } else { kernel_table = gt->gt_gnext; gtnptr = &kernel_table; } if (gt->gt_gnext) gt->gt_gnext->gt_gprev = gt->gt_gprev; #ifdef RSRR /* Send route change notification to reservation protocol. */ rsrr_cache_send(gt,0); rsrr_cache_clean(gt); #endif /* RSRR */ free((char *)gt); } else { if (gt->gt_prsent_timer == -1) { if (gt->gt_grpmems == 0) send_prune(gt); else gt->gt_prsent_timer = 0; } gtnptr = >->gt_gnext; } } /* * When traversing the no_route table, the decision is much easier. * Just delete it if it has timed out. */ gtnptr = &kernel_no_route; while ((gt = *gtnptr) != NULL) { /* advance the timer for the kernel entry */ gt->gt_timer -= ROUTE_MAX_REPORT_DELAY; if (gt->gt_timer < 0) { if (gt->gt_srctbl) { if (k_del_rg(gt->gt_srctbl->st_origin, gt) < 0) { logit(LOG_WARNING, errno, "%s (%s %s)", "age_table_entry trying to delete no-route", inet_fmt(gt->gt_srctbl->st_origin), inet_fmt(gt->gt_mcastgrp)); } free(gt->gt_srctbl); } *gtnptr = gt->gt_next; if (gt->gt_next) gt->gt_next->gt_prev = gt->gt_prev; free((char *)gt); } else { gtnptr = >->gt_next; } } } /* * Modify the kernel to forward packets when one or multiple prunes that * were received on the vif given by vifi, for the group given by gt, * have expired. */ static void expire_prune(vifi_t vifi, struct gtable *gt) { /* * No need to send a graft, any prunes that we sent * will expire before any prunes that we have received. */ if (gt->gt_prsent_timer > 0) { logit(LOG_DEBUG, 0, "prune expired with %d left on %s", gt->gt_prsent_timer, "prsent_timer"); gt->gt_prsent_timer = 0; } /* modify the kernel entry to forward packets */ if (!VIFM_ISSET(vifi, gt->gt_grpmems)) { struct rtentry *rt = gt->gt_route; VIFM_SET(vifi, gt->gt_grpmems); logit(LOG_DEBUG, 0, "forw again (%s %s) gm:%x vif:%d", inet_fmts(rt->rt_origin, rt->rt_originmask), inet_fmt(gt->gt_mcastgrp), gt->gt_grpmems, vifi); prun_add_ttls(gt); update_kernel(gt); #ifdef RSRR /* Send route change notification to reservation protocol. */ rsrr_cache_send(gt,1); #endif /* RSRR */ } } static const char * scaletime(u_long t) { static char buf1[5]; static char buf2[5]; static char *buf=buf1; char s; char *p; p = buf; if (buf == buf1) buf = buf2; else buf = buf1; if (t < 120) { s = 's'; } else if (t < 3600) { t /= 60; s = 'm'; } else if (t < 86400) { t /= 3600; s = 'h'; } else if (t < 864000) { t /= 86400; s = 'd'; } else { t /= 604800; s = 'w'; } if (t > 999) return "*** "; snprintf(p, 5, "%3d%c", (int)t, s); return p; } /* * Print the contents of the cache table on file 'fp2'. */ void dump_cache(FILE *fp2) { struct rtentry *r; struct gtable *gt; struct stable *st; vifi_t i; time_t thyme = time(0); fprintf(fp2, "Multicast Routing Cache Table (%d entries)\n%s", kroutes, " Origin Mcast-group CTmr Age Ptmr IVif Forwvifs\n"); for (gt = kernel_no_route; gt; gt = gt->gt_next) { if (gt->gt_srctbl) { fprintf(fp2, " %-18s %-15s %-4s %-4s - -1\n", inet_fmts(gt->gt_srctbl->st_origin, 0xffffffff), inet_fmt(gt->gt_mcastgrp), scaletime(gt->gt_timer), scaletime(thyme - gt->gt_ctime)); fprintf(fp2, ">%s\n", inet_fmt(gt->gt_srctbl->st_origin)); } } for (gt = kernel_table; gt; gt = gt->gt_gnext) { r = gt->gt_route; fprintf(fp2, " %-18s %-15s", inet_fmts(r->rt_origin, r->rt_originmask), inet_fmt(gt->gt_mcastgrp)); fprintf(fp2, " %-4s", scaletime(gt->gt_timer)); fprintf(fp2, " %-4s %-4s ", scaletime(thyme - gt->gt_ctime), gt->gt_prsent_timer ? scaletime(gt->gt_prsent_timer) : " -"); fprintf(fp2, "%2u%c%c ", r->rt_parent, gt->gt_prsent_timer ? 'P' : ' ', VIFM_ISSET(r->rt_parent, gt->gt_scope) ? 'B' : ' '); for (i = 0; i < numvifs; ++i) { if (VIFM_ISSET(i, gt->gt_grpmems)) fprintf(fp2, " %u ", i); else if (VIFM_ISSET(i, r->rt_children) && !VIFM_ISSET(i, r->rt_leaves)) fprintf(fp2, " %u%c", i, VIFM_ISSET(i, gt->gt_scope) ? 'b' : 'p'); } fprintf(fp2, "\n"); for (st = gt->gt_srctbl; st; st = st->st_next) { fprintf(fp2, ">%s\n", inet_fmt(st->st_origin)); } #ifdef DEBUG_PRUNES for (pt = gt->gt_pruntbl; pt; pt = pt->pt_next) { fprintf(fp2, "pt_router), pt->pt_vifi, pt->pt_timer); } #endif } } /* * Traceroute function which returns traceroute replies to the requesting * router. Also forwards the request to downstream routers. * * no: promoted u_char */ void accept_mtrace(u_int32_t src, u_int32_t dst, u_int32_t group, char *data, u_int no, int datalen) { u_char type; struct rtentry *rt; struct gtable *gt; struct tr_query *qry; struct tr_resp *resp; int vifi; char *p; int rcount; int errcode = TR_NO_ERR; int resptype; struct timeval tp; struct sioc_vif_req v_req; struct sioc_sg_req sg_req; /* Remember qid across invocations */ static u_int32_t oqid = 0; /* timestamp the request/response */ gettimeofday(&tp, 0); /* * Check if it is a query or a response */ if (datalen == QLEN) { type = QUERY; logit(LOG_DEBUG, 0, "Initial traceroute query rcvd from %s to %s", inet_fmt(src), inet_fmt(dst)); } else if ((datalen - QLEN) % RLEN == 0) { type = RESP; logit(LOG_DEBUG, 0, "In-transit traceroute query rcvd from %s to %s", inet_fmt(src), inet_fmt(dst)); if (IN_MULTICAST(ntohl(dst))) { logit(LOG_DEBUG, 0, "Dropping multicast response"); return; } } else { logit(LOG_WARNING, 0, "%s from %s to %s", "Non decipherable traceroute request received", inet_fmt(src), inet_fmt(dst)); return; } qry = (struct tr_query *)data; /* * if it is a packet with all reports filled, drop it */ if ((u_int)(rcount = (datalen - QLEN)/RLEN) == no) { logit(LOG_DEBUG, 0, "packet with all reports filled in"); return; } logit(LOG_DEBUG, 0, "s: %s g: %s d: %s ", inet_fmt(qry->tr_src), inet_fmt(group), inet_fmt(qry->tr_dst)); logit(LOG_DEBUG, 0, "rttl: %d rd: %s", qry->tr_rttl, inet_fmt(qry->tr_raddr)); logit(LOG_DEBUG, 0, "rcount:%d, qid:%06x", rcount, qry->tr_qid); /* determine the routing table entry for this traceroute */ rt = determine_route(qry->tr_src); if (rt) { logit(LOG_DEBUG, 0, "rt parent vif: %d rtr: %s metric: %d", rt->rt_parent, inet_fmt(rt->rt_gateway), rt->rt_metric); logit(LOG_DEBUG, 0, "rt origin %s", inet_fmts(rt->rt_origin, rt->rt_originmask)); } else logit(LOG_DEBUG, 0, "...no route"); /* * Query type packet - check if rte exists * Check if the query destination is a vif connected to me. * and if so, whether I should start response back */ if (type == QUERY) { if (oqid == qry->tr_qid) { /* * If the multicast router is a member of the group being * queried, and the query is multicasted, then the router can * receive multiple copies of the same query. If we have already * replied to this traceroute, just ignore it this time. * * This is not a total solution, but since if this fails you * only get N copies, N <= the number of interfaces on the router, * it is not fatal. */ logit(LOG_DEBUG, 0, "ignoring duplicate traceroute packet"); return; } if (rt == NULL) { logit(LOG_DEBUG, 0, "Mcast traceroute: no route entry %s", inet_fmt(qry->tr_src)); if (IN_MULTICAST(ntohl(dst))) return; } vifi = find_vif(qry->tr_dst, 0); if (vifi == NO_VIF) { /* The traceroute destination is not on one of my subnet vifs. */ logit(LOG_DEBUG, 0, "Destination %s not an interface", inet_fmt(qry->tr_dst)); if (IN_MULTICAST(ntohl(dst))) return; errcode = TR_WRONG_IF; } else if (rt != NULL && !VIFM_ISSET(vifi, rt->rt_children)) { logit(LOG_DEBUG, 0, "Destination %s not on forwarding tree for src %s", inet_fmt(qry->tr_dst), inet_fmt(qry->tr_src)); if (IN_MULTICAST(ntohl(dst))) return; errcode = TR_WRONG_IF; } } else { /* * determine which interface the packet came in on * RESP packets travel hop-by-hop so this either traversed * a tunnel or came from a directly attached mrouter. */ if ((vifi = find_vif(src, dst)) == NO_VIF) { logit(LOG_DEBUG, 0, "Wrong interface for packet"); errcode = TR_WRONG_IF; } } /* Now that we've decided to send a response, save the qid */ oqid = qry->tr_qid; logit(LOG_DEBUG, 0, "Sending traceroute response"); /* copy the packet to the sending buffer */ p = send_buf + MIN_IP_HEADER_LEN + IGMP_MINLEN; bcopy(data, p, datalen); p += datalen; /* * If there is no room to insert our reply, coopt the previous hop * error indication to relay this fact. */ if (p + sizeof(struct tr_resp) > send_buf + RECV_BUF_SIZE) { resp = (struct tr_resp *)p - 1; resp->tr_rflags = TR_NO_SPACE; rt = NULL; goto sendit; } /* * fill in initial response fields */ resp = (struct tr_resp *)p; bzero(resp, sizeof(struct tr_resp)); datalen += RLEN; resp->tr_qarr = htonl((tp.tv_sec + JAN_1970) << 16) + ((tp.tv_usec >> 4) & 0xffff); resp->tr_rproto = PROTO_DVMRP; if (errcode != TR_NO_ERR) { resp->tr_rflags = errcode; rt = NULL; /* hack to enforce send straight to requestor */ goto sendit; } resp->tr_outaddr = uvifs[vifi].uv_lcl_addr; resp->tr_fttl = uvifs[vifi].uv_threshold; resp->tr_rflags = TR_NO_ERR; /* * obtain # of packets out on interface */ v_req.vifi = vifi; if (ioctl(igmp_socket, SIOCGETVIFCNT, (char *)&v_req) >= 0) resp->tr_vifout = htonl(v_req.ocount); /* * fill in scoping & pruning information */ if (rt) for (gt = rt->rt_groups; gt; gt = gt->gt_next) { if (gt->gt_mcastgrp >= group) break; } else gt = NULL; if (gt && gt->gt_mcastgrp == group) { sg_req.src.s_addr = qry->tr_src; sg_req.grp.s_addr = group; if (ioctl(igmp_socket, SIOCGETSGCNT, (char *)&sg_req) >= 0) resp->tr_pktcnt = htonl(sg_req.pktcnt); if (VIFM_ISSET(vifi, gt->gt_scope)) resp->tr_rflags = TR_SCOPED; else if (gt->gt_prsent_timer) resp->tr_rflags = TR_PRUNED; else if (!VIFM_ISSET(vifi, gt->gt_grpmems)) { if (VIFM_ISSET(vifi, rt->rt_children) && !VIFM_ISSET(vifi, rt->rt_leaves)) resp->tr_rflags = TR_OPRUNED; else resp->tr_rflags = TR_NO_FWD; } } else { if (scoped_addr(vifi, group)) resp->tr_rflags = TR_SCOPED; else if (rt && !VIFM_ISSET(vifi, rt->rt_children)) resp->tr_rflags = TR_NO_FWD; } /* * if no rte exists, set NO_RTE error */ if (rt == NULL) { src = dst; /* the dst address of resp. pkt */ resp->tr_inaddr = 0; resp->tr_rflags = TR_NO_RTE; resp->tr_rmtaddr = 0; } else { /* get # of packets in on interface */ v_req.vifi = rt->rt_parent; if (ioctl(igmp_socket, SIOCGETVIFCNT, (char *)&v_req) >= 0) resp->tr_vifin = htonl(v_req.icount); MASK_TO_VAL(rt->rt_originmask, resp->tr_smask); src = uvifs[rt->rt_parent].uv_lcl_addr; resp->tr_inaddr = src; resp->tr_rmtaddr = rt->rt_gateway; if (!VIFM_ISSET(vifi, rt->rt_children)) { logit(LOG_DEBUG, 0, "Destination %s not on forwarding tree for src %s", inet_fmt(qry->tr_dst), inet_fmt(qry->tr_src)); resp->tr_rflags = TR_WRONG_IF; } if (rt->rt_metric >= UNREACHABLE) { resp->tr_rflags = TR_NO_RTE; /* Hack to send reply directly */ rt = NULL; } } sendit: /* * if metric is 1 or no. of reports is 1, send response to requestor * else send to upstream router. If the upstream router can't handle * mtrace, set an error code and send to requestor anyway. */ logit(LOG_DEBUG, 0, "rcount:%d, no:%d", rcount, no); if (((u_int)(rcount + 1) == no) || (rt == NULL) || (rt->rt_metric == 1)) { resptype = IGMP_MTRACE_REPLY; dst = qry->tr_raddr; } else if (!can_mtrace(rt->rt_parent, rt->rt_gateway)) { dst = qry->tr_raddr; resp->tr_rflags = TR_OLD_ROUTER; resptype = IGMP_MTRACE_REPLY; } else { dst = rt->rt_gateway; resptype = IGMP_MTRACE_QUERY; } if (IN_MULTICAST(ntohl(dst))) { /* * Send the reply on a known multicast capable vif. * If we don't have one, we can't source any multicasts anyway. */ if (phys_vif != -1) { logit(LOG_DEBUG, 0, "Sending reply to %s from %s", inet_fmt(dst), inet_fmt(uvifs[phys_vif].uv_lcl_addr)); k_set_ttl(qry->tr_rttl); send_igmp(uvifs[phys_vif].uv_lcl_addr, dst, resptype, no, group, datalen); k_set_ttl(1); } else logit(LOG_INFO, 0, "No enabled phyints -- %s", "dropping traceroute reply"); } else { logit(LOG_DEBUG, 0, "Sending %s to %s from %s", resptype == IGMP_MTRACE_REPLY ? "reply" : "request on", inet_fmt(dst), inet_fmt(src)); send_igmp(src, dst, resptype, no, group, datalen); } return; }