📄 igmp.c
字号:
/* * Linux NET3: Internet Group Management Protocol [IGMP] * * This code implements the IGMP protocol as defined in RFC1112. There has * been a further revision of this protocol since which is now supported. * * If you have trouble with this module be careful what gcc you have used, * the older version didn't come out right using gcc 2.5.8, the newer one * seems to fall out with gcc 2.6.2. * * Version: $Id: igmp.c,v 1.46 2001/07/27 09:27:29 davem Exp $ * * Authors: * Alan Cox <Alan.Cox@linux.org> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. * * Fixes: * * Alan Cox : Added lots of __inline__ to optimise * the memory usage of all the tiny little * functions. * Alan Cox : Dumped the header building experiment. * Alan Cox : Minor tweaks ready for multicast routing * and extended IGMP protocol. * Alan Cox : Removed a load of inline directives. Gcc 2.5.8 * writes utterly bogus code otherwise (sigh) * fixed IGMP loopback to behave in the manner * desired by mrouted, fixed the fact it has been * broken since 1.3.6 and cleaned up a few minor * points. * * Chih-Jen Chang : Tried to revise IGMP to Version 2 * Tsu-Sheng Tsao E-mail: chihjenc@scf.usc.edu and tsusheng@scf.usc.edu * The enhancements are mainly based on Steve Deering's * ipmulti-3.5 source code. * Chih-Jen Chang : Added the igmp_get_mrouter_info and * Tsu-Sheng Tsao igmp_set_mrouter_info to keep track of * the mrouted version on that device. * Chih-Jen Chang : Added the max_resp_time parameter to * Tsu-Sheng Tsao igmp_heard_query(). Using this parameter * to identify the multicast router version * and do what the IGMP version 2 specified. * Chih-Jen Chang : Added a timer to revert to IGMP V2 router * Tsu-Sheng Tsao if the specified time expired. * Alan Cox : Stop IGMP from 0.0.0.0 being accepted. * Alan Cox : Use GFP_ATOMIC in the right places. * Christian Daudt : igmp timer wasn't set for local group * memberships but was being deleted, * which caused a "del_timer() called * from %p with timer not initialized\n" * message (960131). * Christian Daudt : removed del_timer from * igmp_timer_expire function (960205). * Christian Daudt : igmp_heard_report now only calls * igmp_timer_expire if tm->running is * true (960216). * Malcolm Beattie : ttl comparison wrong in igmp_rcv made * igmp_heard_query never trigger. Expiry * miscalculation fixed in igmp_heard_query * and random() made to return unsigned to * prevent negative expiry times. * Alexey Kuznetsov: Wrong group leaving behaviour, backport * fix from pending 2.1.x patches. * Alan Cox: Forget to enable FDDI support earlier. * Alexey Kuznetsov: Fixed leaving groups on device down. * Alexey Kuznetsov: Accordance to igmp-v2-06 draft. */#include <linux/config.h>#include <asm/uaccess.h>#include <asm/system.h>#include <linux/types.h>#include <linux/kernel.h>#include <linux/sched.h>#include <linux/string.h>#include <linux/socket.h>#include <linux/sockios.h>#include <linux/in.h>#include <linux/inet.h>#include <linux/netdevice.h>#include <linux/skbuff.h>#include <linux/inetdevice.h>#include <linux/igmp.h>#include <linux/if_arp.h>#include <linux/rtnetlink.h>#include <net/ip.h>#include <net/protocol.h>#include <net/route.h>#include <net/sock.h>#include <net/checksum.h>#include <linux/netfilter_ipv4.h>#ifdef CONFIG_IP_MROUTE#include <linux/mroute.h>#endif#define IP_MAX_MEMBERSHIPS 20#ifdef CONFIG_IP_MULTICAST/* Parameter names and values are taken from igmp-v2-06 draft */#define IGMP_V1_Router_Present_Timeout (400*HZ)#define IGMP_Unsolicited_Report_Interval (10*HZ)#define IGMP_Query_Response_Interval (10*HZ)#define IGMP_Unsolicited_Report_Count 2#define IGMP_Initial_Report_Delay (1)/* IGMP_Initial_Report_Delay is not from IGMP specs! * IGMP specs require to report membership immediately after * joining a group, but we delay the first report by a * small interval. It seems more natural and still does not * contradict to specs provided this delay is small enough. */#define IGMP_V1_SEEN(in_dev) ((in_dev)->mr_v1_seen && (long)(jiffies - (in_dev)->mr_v1_seen) < 0)#endifstatic void ip_ma_put(struct ip_mc_list *im){ if (atomic_dec_and_test(&im->refcnt)) { in_dev_put(im->interface); kfree(im); }}#ifdef CONFIG_IP_MULTICAST/* * Timer management */static __inline__ void igmp_stop_timer(struct ip_mc_list *im){ spin_lock_bh(&im->lock); if (del_timer(&im->timer)) atomic_dec(&im->refcnt); im->tm_running=0; im->reporter = 0; im->unsolicit_count = 0; spin_unlock_bh(&im->lock);}/* It must be called with locked im->lock */static void igmp_start_timer(struct ip_mc_list *im, int max_delay){ int tv=net_random() % max_delay; im->tm_running=1; if (!mod_timer(&im->timer, jiffies+tv+2)) atomic_inc(&im->refcnt);}static void igmp_mod_timer(struct ip_mc_list *im, int max_delay){ spin_lock_bh(&im->lock); im->unsolicit_count = 0; if (del_timer(&im->timer)) { if ((long)(im->timer.expires-jiffies) < max_delay) { add_timer(&im->timer); im->tm_running=1; spin_unlock_bh(&im->lock); return; } atomic_dec(&im->refcnt); } igmp_start_timer(im, max_delay); spin_unlock_bh(&im->lock);}/* * Send an IGMP report. */#define IGMP_SIZE (sizeof(struct igmphdr)+sizeof(struct iphdr)+4)/* Don't just hand NF_HOOK skb->dst->output, in case netfilter hook changes route */static inline intoutput_maybe_reroute(struct sk_buff *skb){ return skb->dst->output(skb);}static int igmp_send_report(struct net_device *dev, u32 group, int type){ struct sk_buff *skb; struct iphdr *iph; struct igmphdr *ih; struct rtable *rt; u32 dst; /* According to IGMPv2 specs, LEAVE messages are * sent to all-routers group. */ dst = group; if (type == IGMP_HOST_LEAVE_MESSAGE) dst = IGMP_ALL_ROUTER; if (ip_route_output(&rt, dst, 0, 0, dev->ifindex)) return -1; if (rt->rt_src == 0) { ip_rt_put(rt); return -1; } skb=alloc_skb(IGMP_SIZE+dev->hard_header_len+15, GFP_ATOMIC); if (skb == NULL) { ip_rt_put(rt); return -1; } skb->dst = &rt->u.dst; skb_reserve(skb, (dev->hard_header_len+15)&~15); skb->nh.iph = iph = (struct iphdr *)skb_put(skb, sizeof(struct iphdr)+4); iph->version = 4; iph->ihl = (sizeof(struct iphdr)+4)>>2; iph->tos = 0; iph->frag_off = __constant_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, output_maybe_reroute);}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; int err; spin_lock(&im->lock); im->tm_running=0; if (IGMP_V1_SEEN(in_dev)) err = igmp_send_report(in_dev->dev, im->multiaddr, IGMP_HOST_MEMBERSHIP_REPORT); else err = igmp_send_report(in_dev->dev, im->multiaddr, IGMP_HOST_NEW_MEMBERSHIP_REPORT); /* Failed. Retry later. */ if (err) { if (!in_dev->dead) igmp_start_timer(im, IGMP_Unsolicited_Report_Interval); goto out; } if (im->unsolicit_count) { im->unsolicit_count--; igmp_start_timer(im, IGMP_Unsolicited_Report_Interval); } im->reporter = 1;out: spin_unlock(&im->lock); ip_ma_put(im);}static void igmp_heard_report(struct in_device *in_dev, u32 group){ struct ip_mc_list *im; /* Timers are only set for non-local groups */ if (group == IGMP_ALL_HOSTS) return; read_lock(&in_dev->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->lock);}static void igmp_heard_query(struct in_device *in_dev, unsigned char max_resp_time, u32 group){ struct ip_mc_list *im; int max_delay; max_delay = max_resp_time*(HZ/IGMP_TIMER_SCALE); if (max_resp_time == 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; } /* * - 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->lock); for (im=in_dev->mc_list; im!=NULL; im=im->next) { if (group && group != im->multiaddr) continue; if (im->multiaddr == IGMP_ALL_HOSTS) continue; igmp_mod_timer(im, max_delay); } read_unlock(&in_dev->lock);}int igmp_rcv(struct sk_buff *skb){ /* This basically follows the spec line by line -- see RFC1112 */ struct igmphdr *ih = skb->h.igmph; struct in_device *in_dev = in_dev_get(skb->dev); int len = skb->len; if (in_dev==NULL) { kfree_skb(skb); return 0; } if (skb_is_nonlinear(skb)) { if (skb_linearize(skb, GFP_ATOMIC) != 0) { kfree_skb(skb); return -ENOMEM; } ih = skb->h.igmph; } if (len < sizeof(struct igmphdr) || ip_compute_csum((void *)ih, len)) { in_dev_put(in_dev); kfree_skb(skb); return 0; } switch (ih->type) { case IGMP_HOST_MEMBERSHIP_QUERY: igmp_heard_query(in_dev, ih->code, ih->group); break; case IGMP_HOST_MEMBERSHIP_REPORT: case IGMP_HOST_NEW_MEMBERSHIP_REPORT: /* Is it our report looped back? */ if (((struct rtable*)skb->dst)->key.iif == 0) break; 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: NETDEBUG(printk(KERN_DEBUG "New IGMP type=%d, why we do not know about it?\n", ih->type)); } 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, u32 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 */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -