📄 igmp.c
字号:
} read_unlock(&in_dev->mc_list_lock); } else { spin_lock_bh(&pmc->lock); if (pmc->sfcount[MCAST_EXCLUDE]) type = IGMPV3_MODE_IS_EXCLUDE; else type = IGMPV3_MODE_IS_INCLUDE; skb = add_grec(skb, pmc, type, 0, 0); spin_unlock_bh(&pmc->lock); } if (!skb) return 0; return igmpv3_sendpack(skb);}/* * remove zero-count source records from a source filter list */static void igmpv3_clear_zeros(struct ip_sf_list **ppsf){ struct ip_sf_list *psf_prev, *psf_next, *psf; psf_prev = NULL; for (psf=*ppsf; psf; psf = psf_next) { psf_next = psf->sf_next; if (psf->sf_crcount == 0) { if (psf_prev) psf_prev->sf_next = psf->sf_next; else *ppsf = psf->sf_next; kfree(psf); } else psf_prev = psf; }}static void igmpv3_send_cr(struct in_device *in_dev){ struct ip_mc_list *pmc, *pmc_prev, *pmc_next; struct sk_buff *skb = NULL; int type, dtype; read_lock(&in_dev->mc_list_lock); spin_lock_bh(&in_dev->mc_tomb_lock); /* deleted MCA's */ pmc_prev = NULL; for (pmc=in_dev->mc_tomb; pmc; pmc=pmc_next) { pmc_next = pmc->next; if (pmc->sfmode == MCAST_INCLUDE) { type = IGMPV3_BLOCK_OLD_SOURCES; dtype = IGMPV3_BLOCK_OLD_SOURCES; skb = add_grec(skb, pmc, type, 1, 0); skb = add_grec(skb, pmc, dtype, 1, 1); } if (pmc->crcount) { if (pmc->sfmode == MCAST_EXCLUDE) { type = IGMPV3_CHANGE_TO_INCLUDE; skb = add_grec(skb, pmc, type, 1, 0); } pmc->crcount--; if (pmc->crcount == 0) { igmpv3_clear_zeros(&pmc->tomb); igmpv3_clear_zeros(&pmc->sources); } } if (pmc->crcount == 0 && !pmc->tomb && !pmc->sources) { if (pmc_prev) pmc_prev->next = pmc_next; else in_dev->mc_tomb = pmc_next; in_dev_put(pmc->interface); kfree(pmc); } else pmc_prev = pmc; } spin_unlock_bh(&in_dev->mc_tomb_lock); /* change recs */ for (pmc=in_dev->mc_list; pmc; pmc=pmc->next) { spin_lock_bh(&pmc->lock); if (pmc->sfcount[MCAST_EXCLUDE]) { type = IGMPV3_BLOCK_OLD_SOURCES; dtype = IGMPV3_ALLOW_NEW_SOURCES; } else { type = IGMPV3_ALLOW_NEW_SOURCES; dtype = IGMPV3_BLOCK_OLD_SOURCES; } skb = add_grec(skb, pmc, type, 0, 0); skb = add_grec(skb, pmc, dtype, 0, 1); /* deleted sources */ /* filter mode changes */ if (pmc->crcount) { if (pmc->sfmode == MCAST_EXCLUDE) type = IGMPV3_CHANGE_TO_EXCLUDE; else type = IGMPV3_CHANGE_TO_INCLUDE; skb = add_grec(skb, pmc, type, 0, 0); pmc->crcount--; } spin_unlock_bh(&pmc->lock); } read_unlock(&in_dev->mc_list_lock); if (!skb) return; (void) igmpv3_sendpack(skb);}static int igmp_send_report(struct in_device *in_dev, struct ip_mc_list *pmc, int type){ struct sk_buff *skb; struct iphdr *iph; struct igmphdr *ih; struct rtable *rt; struct net_device *dev = in_dev->dev; __be32 group = pmc ? pmc->multiaddr : 0; __be32 dst; if (type == IGMPV3_HOST_MEMBERSHIP_REPORT) return igmpv3_send_report(in_dev, pmc); else if (type == IGMP_HOST_LEAVE_MESSAGE) dst = IGMP_ALL_ROUTER; else dst = group; { struct flowi fl = { .oif = dev->ifindex, .nl_u = { .ip4_u = { .daddr = dst } }, .proto = IPPROTO_IGMP }; if (ip_route_output_key(&rt, &fl)) return -1; } if (rt->rt_src == 0) { ip_rt_put(rt); return -1; } skb=alloc_skb(IGMP_SIZE+LL_RESERVED_SPACE(dev), GFP_ATOMIC); if (skb == NULL) { ip_rt_put(rt); return -1; } skb->dst = &rt->u.dst; skb_reserve(skb, LL_RESERVED_SPACE(dev)); skb_reset_network_header(skb); iph = ip_hdr(skb); skb_put(skb, sizeof(struct iphdr) + 4); iph->version = 4; iph->ihl = (sizeof(struct iphdr)+4)>>2; iph->tos = 0xc0; iph->frag_off = htons(IP_DF); iph->ttl = 1; iph->daddr = dst; iph->saddr = rt->rt_src; iph->protocol = IPPROTO_IGMP; iph->tot_len = htons(IGMP_SIZE); ip_select_ident(iph, &rt->u.dst, NULL); ((u8*)&iph[1])[0] = IPOPT_RA; ((u8*)&iph[1])[1] = 4; ((u8*)&iph[1])[2] = 0; ((u8*)&iph[1])[3] = 0; ip_send_check(iph); ih = (struct igmphdr *)skb_put(skb, sizeof(struct igmphdr)); ih->type=type; ih->code=0; ih->csum=0; ih->group=group; ih->csum=ip_compute_csum((void *)ih, sizeof(struct igmphdr)); return NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, rt->u.dst.dev, dst_output);}static void igmp_gq_timer_expire(unsigned long data){ struct in_device *in_dev = (struct in_device *)data; in_dev->mr_gq_running = 0; igmpv3_send_report(in_dev, NULL); __in_dev_put(in_dev);}static void igmp_ifc_timer_expire(unsigned long data){ struct in_device *in_dev = (struct in_device *)data; igmpv3_send_cr(in_dev); if (in_dev->mr_ifc_count) { in_dev->mr_ifc_count--; igmp_ifc_start_timer(in_dev, IGMP_Unsolicited_Report_Interval); } __in_dev_put(in_dev);}static void igmp_ifc_event(struct in_device *in_dev){ if (IGMP_V1_SEEN(in_dev) || IGMP_V2_SEEN(in_dev)) return; in_dev->mr_ifc_count = in_dev->mr_qrv ? in_dev->mr_qrv : IGMP_Unsolicited_Report_Count; igmp_ifc_start_timer(in_dev, 1);}static void igmp_timer_expire(unsigned long data){ struct ip_mc_list *im=(struct ip_mc_list *)data; struct in_device *in_dev = im->interface; spin_lock(&im->lock); im->tm_running=0; if (im->unsolicit_count) { im->unsolicit_count--; igmp_start_timer(im, IGMP_Unsolicited_Report_Interval); } im->reporter = 1; spin_unlock(&im->lock); if (IGMP_V1_SEEN(in_dev)) igmp_send_report(in_dev, im, IGMP_HOST_MEMBERSHIP_REPORT); else if (IGMP_V2_SEEN(in_dev)) igmp_send_report(in_dev, im, IGMPV2_HOST_MEMBERSHIP_REPORT); else igmp_send_report(in_dev, im, IGMPV3_HOST_MEMBERSHIP_REPORT); ip_ma_put(im);}/* mark EXCLUDE-mode sources */static int igmp_xmarksources(struct ip_mc_list *pmc, int nsrcs, __be32 *srcs){ struct ip_sf_list *psf; int i, scount; scount = 0; for (psf=pmc->sources; psf; psf=psf->sf_next) { if (scount == nsrcs) break; for (i=0; i<nsrcs; i++) { /* skip inactive filters */ if (pmc->sfcount[MCAST_INCLUDE] || pmc->sfcount[MCAST_EXCLUDE] != psf->sf_count[MCAST_EXCLUDE]) continue; if (srcs[i] == psf->sf_inaddr) { scount++; break; } } } pmc->gsquery = 0; if (scount == nsrcs) /* all sources excluded */ return 0; return 1;}static int igmp_marksources(struct ip_mc_list *pmc, int nsrcs, __be32 *srcs){ struct ip_sf_list *psf; int i, scount; if (pmc->sfmode == MCAST_EXCLUDE) return igmp_xmarksources(pmc, nsrcs, srcs); /* mark INCLUDE-mode sources */ scount = 0; for (psf=pmc->sources; psf; psf=psf->sf_next) { if (scount == nsrcs) break; for (i=0; i<nsrcs; i++) if (srcs[i] == psf->sf_inaddr) { psf->sf_gsresp = 1; scount++; break; } } if (!scount) { pmc->gsquery = 0; return 0; } pmc->gsquery = 1; return 1;}static void igmp_heard_report(struct in_device *in_dev, __be32 group){ struct ip_mc_list *im; /* Timers are only set for non-local groups */ if (group == IGMP_ALL_HOSTS) return; read_lock(&in_dev->mc_list_lock); for (im=in_dev->mc_list; im!=NULL; im=im->next) { if (im->multiaddr == group) { igmp_stop_timer(im); break; } } read_unlock(&in_dev->mc_list_lock);}static void igmp_heard_query(struct in_device *in_dev, struct sk_buff *skb, int len){ struct igmphdr *ih = igmp_hdr(skb); struct igmpv3_query *ih3 = igmpv3_query_hdr(skb); struct ip_mc_list *im; __be32 group = ih->group; int max_delay; int mark = 0; if (len == 8) { if (ih->code == 0) { /* Alas, old v1 router presents here. */ max_delay = IGMP_Query_Response_Interval; in_dev->mr_v1_seen = jiffies + IGMP_V1_Router_Present_Timeout; group = 0; } else { /* v2 router present */ max_delay = ih->code*(HZ/IGMP_TIMER_SCALE); in_dev->mr_v2_seen = jiffies + IGMP_V2_Router_Present_Timeout; } /* cancel the interface change timer */ in_dev->mr_ifc_count = 0; if (del_timer(&in_dev->mr_ifc_timer)) __in_dev_put(in_dev); /* clear deleted report items */ igmpv3_clear_delrec(in_dev); } else if (len < 12) { return; /* ignore bogus packet; freed by caller */ } else { /* v3 */ if (!pskb_may_pull(skb, sizeof(struct igmpv3_query))) return; ih3 = igmpv3_query_hdr(skb); if (ih3->nsrcs) { if (!pskb_may_pull(skb, sizeof(struct igmpv3_query) + ntohs(ih3->nsrcs)*sizeof(__be32))) return; ih3 = igmpv3_query_hdr(skb); } max_delay = IGMPV3_MRC(ih3->code)*(HZ/IGMP_TIMER_SCALE); if (!max_delay) max_delay = 1; /* can't mod w/ 0 */ in_dev->mr_maxdelay = max_delay; if (ih3->qrv) in_dev->mr_qrv = ih3->qrv; if (!group) { /* general query */ if (ih3->nsrcs) return; /* no sources allowed */ igmp_gq_start_timer(in_dev); return; } /* mark sources to include, if group & source-specific */ mark = ih3->nsrcs != 0; } /* * - Start the timers in all of our membership records * that the query applies to for the interface on * which the query arrived excl. those that belong * to a "local" group (224.0.0.X) * - For timers already running check if they need to * be reset. * - Use the igmp->igmp_code field as the maximum * delay possible */ read_lock(&in_dev->mc_list_lock); for (im=in_dev->mc_list; im!=NULL; im=im->next) { int changed; if (group && group != im->multiaddr) continue; if (im->multiaddr == IGMP_ALL_HOSTS) continue; spin_lock_bh(&im->lock); if (im->tm_running) im->gsquery = im->gsquery && mark; else im->gsquery = mark; changed = !im->gsquery || igmp_marksources(im, ntohs(ih3->nsrcs), ih3->srcs); spin_unlock_bh(&im->lock); if (changed) igmp_mod_timer(im, max_delay); } read_unlock(&in_dev->mc_list_lock);}int igmp_rcv(struct sk_buff *skb){ /* This basically follows the spec line by line -- see RFC1112 */ struct igmphdr *ih; struct in_device *in_dev = in_dev_get(skb->dev); int len = skb->len; if (in_dev==NULL) { kfree_skb(skb); return 0; } if (!pskb_may_pull(skb, sizeof(struct igmphdr))) goto drop; switch (skb->ip_summed) { case CHECKSUM_COMPLETE: if (!csum_fold(skb->csum)) break; /* fall through */ case CHECKSUM_NONE: skb->csum = 0; if (__skb_checksum_complete(skb)) goto drop; } ih = igmp_hdr(skb); switch (ih->type) { case IGMP_HOST_MEMBERSHIP_QUERY: igmp_heard_query(in_dev, skb, len); break; case IGMP_HOST_MEMBERSHIP_REPORT: case IGMPV2_HOST_MEMBERSHIP_REPORT: case IGMPV3_HOST_MEMBERSHIP_REPORT: /* Is it our report looped back? */ if (((struct rtable*)skb->dst)->fl.iif == 0) break; /* don't rely on MC router hearing unicast reports */ if (skb->pkt_type == PACKET_MULTICAST || skb->pkt_type == PACKET_BROADCAST) igmp_heard_report(in_dev, ih->group); break; case IGMP_PIM:#ifdef CONFIG_IP_PIMSM_V1 in_dev_put(in_dev); return pim_rcv_v1(skb);#endif case IGMP_DVMRP: case IGMP_TRACE: case IGMP_HOST_LEAVE_MESSAGE: case IGMP_MTRACE: case IGMP_MTRACE_RESP: break; default: break; }drop: in_dev_put(in_dev); kfree_skb(skb); return 0;}#endif/* * Add a filter to a device */static void ip_mc_filter_add(struct in_device *in_dev, __be32 addr){ char buf[MAX_ADDR_LEN]; struct net_device *dev = in_dev->dev; /* Checking for IFF_MULTICAST here is WRONG-WRONG-WRONG. We will get multicast token leakage, when IFF_MULTICAST is changed. This check should be done in dev->set_multicast_list routine. Something sort of: if (dev->mc_list && dev->flags&IFF_MULTICAST) { do it; } --ANK */ if (arp_mc_map(addr, buf, dev, 0) == 0) dev_mc_add(dev,buf,dev->addr_len,0);}/* * Remove a filter from a device */static void ip_mc_filter_del(struct in_device *in_dev, __be32 addr){ char buf[MAX_ADDR_LEN]; struct net_device *dev = in_dev->dev; if (arp_mc_map(addr, buf, dev, 0) == 0) dev_mc_delete(dev,buf,dev->addr_len,0);}#ifdef CONFIG_IP_MULTICAST/* * deleted ip_mc_list manipulation */static void igmpv3_add_delrec(struct in_device *in_dev, struct ip_mc_list *im){ struct ip_mc_list *pmc; /* this is an "ip_mc_list" for convenience; only the fields below * are actually used. In particular, the refcnt and users are not * used for management of the delete list. Using the same structure * for deleted items allows change reports to use common code with * non-deleted or query-response MCA's. */ pmc = kzalloc(sizeof(*pmc), GFP_KERNEL); if (!pmc)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -