IGMP (Internet Group Management Protocol,Internet组管理协议) 用于主机和路由器之间,它让多播路由器知道在直连网络上是否有主机加入了多播组,这样路由器才会知道将多播数据向哪些接口转发。当然,路由器和路由器之间也可以使用IGMP,加入组的路由器的行为和这里host的行为是一致的。IGMPv1定义在RFC1112中。像ICMP一样,IGMP也被认为是IP层的一部分。
IGMP报文传输时封装在IP数据报中, IP头部中的协议字段2指明这是一个IGMP报文。
IGMPv1的报文格式比较简单,只有8个字节,格式如下:
0 1 2 3
0 1 23 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version| Type | Unused | Checksum
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Group Address
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
·版本:IGMPv1 该值为1
·消息类型:IGMPv1有两种消息类型。类型为1的是多播路由器发出的查询报文,类型为2的是主机发出的报告报文。
·组地址:组地址为D类IP地址,也就是高字节以1110开头的地址。在查询报文中组地址设置为0,在报告报文中组地址为主机所在的组地址。
多播路由器需要知道在各个接口的直连网络上是否有主机加入了多播组以及加入了哪些组,这样才能知道是否要向这些接口转发多播数据包。也就是说,路由器只需要知道网络上是否有主机加入了组,而不用知道有多少主机以及具体哪些主机加入了组。路由器会为每个接口维护一张表,表中记录了当前至少还包含一个主机的多播组。路由器会周期性的从每个接口发出IGMP查询报文,以刷新该表,如果在三个查询周期内没收到某组内的主机报告,路由器便别认为在该接口上所有主机都退出了该组,因此也就不用向这个接口转发属于这个组的多播数据了。发出的查询报文目的IP地址是224.0.0.1,组地址是0,TTL值是1。(地址224.0.0.1是所有主机组地址,它表示一个物理网段中的所有具有多播能力的主机和路由器。当主机或路由器初始化后,所有支持多播的接口都要加入这个多播组,属于该组的成员无需发送IGMP报告。)
报告报文是主机发出的,用来告诉路由器目前仍有主机属于该组。当主机刚加入一个组时,主机就会主动发送一个IGMP报告。除此之外,当主机收到IGMP查询报文时也要发送报告报文以作回应。一个接口网络中可能有多台主机都加入到了同一个多播组,为了减少报告报文的数量,主机在收到查询报文时不会立刻响应,而是为每个加入的组都启动一个任意时长的延时timer(0-10秒,一般以IP作为seed),当timer超时后才发出报告报文。如果在定时器超时之前主机收到了其它主机发送的报告报文,该主机就会取消timer不再响应。因为多播路由器并不关心有多少主机属于该组,而是关心是否还有主机属于这个组。报告报文的组地址是主机所在的组地址,目的IP地址也是这个组地址,这样同一网段的主机和路由器都能收到这个报告。当主机需要离开某个组时会悄然离去,而不用发任何消息。
Linux对IGMP的支持是Level 2 (Fullsupport for IP multicasting),也就是支持加入组、离开组以及发送多播数据到主机组。一般Linux充当的是host主机的角色。当然,通过配置kernel选项,Linux也可以被配置成一台多播路由器。从编程角度来说加入离开组主要涉及下面这个函数:
int setsockopt(int s, int level, intoptname, const void *optval, socklen_t optlen);
下面是一个简单的例子,实现了组的加入和离开, 代码中各种值的validation部分被省略:
#include
#include
#include
#include
#include
#include
#define IF_MAX 32
#define GROUP_ADDR "236.1.1.1"
int main(int argc, char *argv[])
{
int i,fd,ret;
struct ip_mreq mreq;
struct ifreq *ifreq_pnt;
struct ifreq ifreq;
struct ifconf ifconf;
struct sockaddr_in *s_addr;
fd= socket(AF_INET, SOCK_DGRAM, 0);
//Get all interfaces
ifconf.ifc_len = IF_MAX * sizeof(struct ifreq);
ifconf.ifc_buf = (char *)malloc(ifconf.ifc_len);
memset(ifconf.ifc_buf, 0, ifconf.ifc_len);
ret= ioctl(fd, SIOCGIFCONF, &ifconf);
ifreq_pnt = (struct ifreq *)ifconf.ifc_buf;
for(i = 0; (i*sizeof(struct ifreq)) < ifconf.ifc_len; i++)
{
s_addr = (struct sockaddr_in*)&ifreq_pnt->ifr_addr;
printf("interface%d-- name: %s address: %s\n", i, (char *)ifreq_pnt->ifr_name,inet_ntoa(s_addr->sin_addr));
ifreq_pnt++;
}
//Join a group on the last interface
inet_aton(GROUP_ADDR, &mreq.imr_multiaddr);
mreq.imr_interface = s_addr->sin_addr;
ret= setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
if(0 == ret)
{
printf("\nJoin group(%s) successfully on interface %s\n",inet_ntoa(mreq.imr_multiaddr), inet_ntoa(mreq.imr_interface));
}
sleep(10);
//Leave the group
ret= setsockopt(fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq));
if(0 == ret)
{
printf("\nLeave group(%s) successfully on interface %s\n",inet_ntoa(mreq.imr_multiaddr), inet_ntoa(mreq.imr_interface));
}
free(ifconf.ifc_buf);
exit(0);
}
使用tshark在接口上抓取数据包观察,可以看到当加入组时,两个Host MembershipReport消息从最后一个接口发出。由于Linux默认是使用IGMPv3, 所以当离开组时,可以看到两个Leave Group的消息也被发出。
参考资料:
《TCP/IP详解(卷一)》
《RFC1112》