网络协议!Wireshark抓包分析IGMP协议及代码实现

IGMP(因特网组管理协议)是一种允许主机向相邻交换机和路由器公布其多播组成员身份的协议。是TCP/IP协议组用来实现动态多播的标准协议。

什么是多播

多播是一种网络传输(一对多),允许一台或多台主机发送单一数据包到多台主机的TCP/IP网络技术。这样做的目的是节省网络带宽。在网络音频/视频广播的应用中(一对多或多对多),当需要将一个节点的信号传送到多个节点时,无论是采用重复点对点通信方式,还是采用广播方式,都会严重浪费网络带宽,只有多播才是最好的选择。

IGMP版本有哪些

到目前为止,IGMP有三个版本,而IGMPv1于1989年由斯坦福大学开发。并在OSI模型的网络层上处于活动状态,1997年更新IGMPv1为IGMPv2版本,在2002年再次更新为IGMPv3。

IGMPv2和IGMPv3主要通过功能扩展了前身(IGMPv1),而基本功能(如通用请求的组地址(0.0.0.0))保持不变。但是,各个扩展的细节如何?

IGMPv1版本

IGMPv1是Internet组管理协议的基础,在IGMPv1中已将0.0.0.0定义为组地址,并将224.0.0.1定义为通用IGMP请求的目标地址。路由器自动发出的这些请求的默认间隔为60秒。

IGMPv1还允许所有支持主机加入合适的多播组成员身份请求,以报告的形式发送到相应的IP多播地址。与后继协议相比,IGMPv1仍然缺少允许主机自行离开组的功能,只有超时会将各自的主机从所在组中删除。

所有IGMP消息均以IP协议号为2(十六进制:0x02)的简单IP数据包进行传输。

在这里插入图片描述
IGMPv1包括两种类型的报文:

在这里插入图片描述

Membership Query:查询器向共享网络上所有主机和路由器发送的查询报文,用于了解哪些组播组存在成员。

在这里插入图片描述
IGMPv1的查询报文,类型为0x11,目的IP地址为224.0.0.1。源IP地址为自己接口的IP地址。在组播地址中,填充为0.0.0.0。

Membership Report:主机向查询器发送的报告报文,用于申请加入某个组播组或者应答查询报文。

在这里插入图片描述
上图为主机向路由器发送的报告报文,类型值为0x12,源IP地址为自己主机的IP地址,目的IP地址为组播地址。

第一个协议版本的IGMPv1标头如下所示:

在这里插入图片描述
Version :IGMPv1版本,值为1。
Type

报文类型。该字段有以下两种取值:0x11:表示普遍组查询报文(Membership Query)。0x12:表示成员报告报文(Membership Report)。

Unused: 该字段在发送时被设为0,并在接收时被忽略。

Checksum:

校验和是IGMP报文长度的16位检测,表示IGMP信息补码之和的补码。Checksum字段在进行校验计算时设为0。当发送报文时,必须计算校验和并插入到Checksum字段中去。当接收报文时,校验和必须在处理该报文之前进行检验。

Group Address: 组播组地址。在普遍组查询报文中,该字段设为0;在成员报告报文中,该字段为成员加入的组播组地址。

IGMPv1代码实现:

static void dissect_igmp_v1(u_char *igmp_data)
{

	int offset = 0;
	unsigned char type;
	printf("======= dissect_igmp_v1========\n");
	type = igmp_data[offset];
	printf("Type: 0x%.2x\n",type);
	offset += 1; /*Type*/
	
	printf("Reserved 0x%x\n",igmp_data[offset]);
	/* skip Reserved */
	offset += 1;

	/* checksum */
	printf("check 0x%x\n",ntohs(*(uint16_t*)(igmp_data + offset)));
	//uint16_t check = in_cksum((uint16_t *)igmp_data + offset, sizeof(*igmp_data));
	//printf("check 0x%x\n",check);
	offset += 2;
	/* group address */
	in_addr ip;
	memcpy(&ip.s_addr, igmp_data + offset, sizeof(ip.s_addr));
	printf("Multicast Address: %s\n", inet_ntoa(ip));

}

IGMPv1主要基于查询响应机制完成组播组管理。当一个网段内有多个组播路由器时,由于它们都可以接收到主机发送的成员报告报文,因此只需要选取其中一台组播路由器发送查询报文就足够了.

IGMPv2版本:

IGMPv2规范的发布日期为1997年,这意味着该标准的首次修订版在协议首次发布后约8年出现。虽然自动请求的组(0.0.0.0)和目标(224.0.0.1)地址保持不变,但默认内部持续时间已增加到125秒。

IGMPv2的工作机制与IGMPv1基本相同,最大的不同之处在于IGMPv2增加了离开组机制。成员主机离开组播组时,会主动发送成员离开报文通知IGMP查询器;

当IGMP查询器收到成员离开报文后,会连续发送特定组查询报文,询问该组播组是否还存在组成员。如果在一段时间内没有收到成员主机发送的报告报文,IGMP查询器将不再维护该组的组成员关系。

而IGMPv2可以使IGMP查询器及时了解到网段内哪些组播组已不存在成员,从而及时更新组成员关系,减少网络中冗余的组播流量。

下面我们来看看IGMPv2报文:

在这里插入图片描述

IGMPv2消息也封装在IP协议编号为2的简单IP数据包中。但是,对IGMP标头进行了较小的更改, IGMPv2报文抓包:

在这里插入图片描述

Type:报文类型

该字段有以下四种取值:

普遍组查询( General Query)
特定组查询( Group-Specific Query )
成员关系报告( Membership Report)
成员离开报文(Leave)

#define IGMP_V1_HOST_MEMBERSHIP_QUERY 0x11
#define IGMP_V1_HOST_MEMBERSHIP_REPORT 0x12
#define IGMP_V2_MEMBERSHIP_REPORT 0x16
#define IGMP_V2_LEAVE_GROUP 0x17

报文类型是“ 0x11”(用于请求),“ 0x16”(用于通知)和“ 0x17”(用于离开消息)。为了向后兼容,还为IGMPv1通知提供了代码“ 0x12”。

Max Response Time:最大响应时间。

成员主机在收到IGMP查询器发送的普遍组查询报文后,需要在最大响应时间内做出回应。该字段仅在IGMP查询报文中有效。

Group Address:组播组地址。

在普遍组查询报文中,该字段设为0.0.0.0。
在特定组查询报文中,该字段为要查询的组播组地址。
在成员报告报文和离开报文中,该字段为成员要加入或离开的组播组地址。

与IGMPv1相比,IGMPv2除了保留原有的普遍组查询报文(Membership Query)和成员报告报文(Membership Report)之外,还增加了两种报文:

成员离开报文(Leave group)
特定组查询报文(Group-Specific Query)

在这里插入图片描述

成员离开组播组时主动向查询器发送的报文,用于宣告自己离开了某个组播组。

特定组查询报文(Group-Specific Query):查询器向共享网段内指定组播组发送的查询报文,用于查询该组播组是否存在成员。

static void dissect_igmp_v2(u_char *igmp_data)
{
	int offset = 0;
	unsigned char type;
	uint8_t tsecs = 0;
	type = igmp_data[offset];
	printf("======= dissect_igmp_v2========\n");
	printf("Type: 0x%.2x\n",type);
	
	offset += 1; /*Type*/
	/* Max Resp Time */
	
	tsecs = igmp_data[offset];
	printf("Max Resp Time: %.1f sec (0x%02x)\n",tsecs*0.1, tsecs);
	offset += 1; /*Max Resp Time*/
	/* checksum */
	printf("Checksum 0x%x\n",ntohs(*(uint16_t*)(igmp_data + offset)));
	offset += 2; /* checksum */
	in_addr ip;
	memcpy(&ip.s_addr,igmp_data + offset,sizeof(ip.s_addr));
	printf("Multicast Address: %s\n",inet_ntoa(ip));

}

与IGMPv1相比,IGMPv2增加了查询器选举和离开组机制。

IGMPv3版本

IGMPv3是Internet组管理协议的第三版,发布于2002年10月。0.0.0.0是组地址,224.0.0.1是常规请求的目标地址。关于标准间隔,协议版本基于其直接前身(125秒)。

那么,与IGMPv2相比,IGMPv3报文包含两大类:查询报文和成员报告报文。IGMPv3没有定义专门的成员离开报文,成员离开通过特定类型的报告报文来传达。

IGMPv3标头还集成在IP数据包(协议编号2)中的IGMPv3中,但是比两个先前的协议要复杂得多,这主要是由于可以指定源地址。查询报文和成员报告报文之间也有特定的区别。

IGMPv3查询报文格式:
在这里插入图片描述
IGMPv2查询报文抓包:

在这里插入图片描述
Type: 查询报文类型值为0x11。
Max Resp Code: 最大响应时间。成员主机在收到IGMP查询器发送的普遍组查询报文后,需要在最大响应时间内做出回应。最大响应代码如下:

static int dissect_v3_max_resp(u_char *igmp_data, int offset)
{

	uint8_t bits;
	uint32_t tsecs;

	bits = igmp_data[offset];
	if (bits&0x80) {
		tsecs = ((bits&IGMP_MAX_RESP_MANT)|0x10);
		tsecs = tsecs << ( ((bits&IGMP_MAX_RESP_EXP) >> 4) + 3);
	} else {
		tsecs = bits;
	}

	printf("Max Resp Time: %.1f sec (0x%02x)\n",tsecs*0.1, bits);

	offset += 1;

	return offset;
}

Group Address: 发送常规查询时,“组地址”字段设置为零,并设置为发送IP地址时要查询的IP多播地址组特定查询或组和源特定查询。

**Resv:**保留的4位字段,没有任何功能,仅包含0。

S:S标志将路由器设置为“ 1”,指示它们在接收到请求时应禁止常规更新(如果值为“ 0”,则该字段为非活动状态)

QRV:如果该字段非0,则表示查询器的健壮系数。如果该字段为0,则表示查询器的健壮系数大于7。

QQIC:用于指定IGMPV3查询的间隔。

**Number of Sources:**源数(N)字段指定多少个源地址存在于查询中。 在常规查询中,该数字为零,或者特定于组的查询,而不特定于特定于组和源的查询查询。 此数字受网络的MTU限制,查询被发送。
Source Address:组播源地址,其数量受到Number of Sources字段值大小的限制。

查询报文代码如下:

static void dissect_igmp_v3_query(u_char *igmp_data)
{
	int offset = 0;;
	unsigned char type;

	printf("======= dissect_igmp_v3_query========\n");
	type = igmp_data[offset];
	printf("Type: 0x%.2x\n",type);
	
	offset += 1; /*Type*/
	
	//max resp code 
	offset = dissect_v3_max_resp(igmp_data,offset);

	printf("Checksum 0x%x\n",ntohs(*(uint16_t*)(igmp_data + offset)));
	//checksum 
	offset += 2;

	//group address
	in_addr ip;
	memcpy(&ip.s_addr,igmp_data + offset,sizeof(ip.s_addr));
	printf("Multicast Address: %s\n",inet_ntoa(ip));
	offset += 4;/*Multicast Address*/
	
	offset += 1;
	
	printf("QQIC : %d\n",igmp_data[offset]);
	offset += 1;/*QQIC*/
	printf("Num Src: %d\n",ntohs(igmp_data[offset]));
}

IGMPv3成员报告报文格式:
在这里插入图片描述
IGMPv3成员报告报文抓包:
在这里插入图片描述
Type报文类型,取值为0x22。其他类型上面已经描述了,这里就不讲了。来看看成员报告代码实现。

static void dissect_igmp_v3_report(u_char *igmp_data)
{

	uint16_t num = 0;
	int offset = 0;
	unsigned char type;
	
	type = igmp_data[offset];
	printf("Type: 0x%.2x\n",type);
	
	offset += 1; /*Type*/
	
	printf("Reserved 0x%x\n",igmp_data[offset]);
	
	/* skip Reserved */
	offset += 1;
	
	printf("Checksum 0x%x\n",ntohs(*(uint16_t*)(igmp_data + offset)));
	
	offset += 2;//checksum 

	offset += 2;/*Reserved*/
	
	/* number of group records */
	num = ntohs(*(uint16_t*)(igmp_data + offset));
	printf("Num Group Records: %d\n",num);
	
	offset += 2;/* Num Group Records */
	
	while(num --)
		dissect_v3_group_record(igmp_data,offset);

}

Grounp Record字段格式:
在这里插入图片描述

Grounp Record字段格式代码实现:

static void dissect_v3_group_record(u_char *igmp_data, int offset)
{

	int old_offset = offset;
	struct group_record_info group_record;
	
	/* record type */
	group_record.record_type = igmp_data[offset];
	printf("record_type: 0x%.2x\n",group_record.record_type);
	offset += 1;

	/* aux data len */
	group_record.adl = igmp_data[offset];
	printf("Aux Data Len: %d\n",group_record.adl);
	offset += 1;

	/*number of sources*/
	group_record.num = ntohs(*(uint16_t*)(igmp_data + offset));
	printf("Num Src: %d\n",group_record.num);
	offset += 2;

	/* multicast address */
	in_addr ip;
	memcpy(&ip.s_addr,igmp_data + offset,sizeof(ip.s_addr));
	printf("Multicast Address: %s\n",inet_ntoa(ip));
	offset += 4;

	if (group_record.num == 0) {
		switch(group_record.record_type) {
		case IGMP_V3_MODE_IS_INCLUDE:
		case IGMP_V3_CHANGE_TO_INCLUDE_MODE:
			printf(" / Leave group %s\n", inet_ntoa(ip));
			break;
		case IGMP_V3_MODE_IS_EXCLUDE:
		case IGMP_V3_CHANGE_TO_EXCLUDE_MODE:
			printf(" / Join group %s for any sources\n", inet_ntoa(ip));
			break;
		case IGMP_V3_ALLOW_NEW_SOURCES:
			printf(" / Group %s, ALLOW_NEW_SOURCES but no source specified (?)\n",inet_ntoa(ip));
			break;
		case IGMP_V3_BLOCK_OLD_SOURCES:
			printf(" / Group %s, BLOCK_OLD_SOURCES but no source specified (?)\n",inet_ntoa(ip));
			break;
		default:
			printf("/ Group %s, unknown record type (?)\n", inet_ntoa(ip));
				break;
		}
	} else {
		switch(group_record.record_type) {
		case IGMP_V3_MODE_IS_INCLUDE:
		case IGMP_V3_CHANGE_TO_INCLUDE_MODE:
			printf(" / Join group %s for source%s {\n",inet_ntoa(ip), (group_record.num > 1) ? "s in" : "");
			break;
		case IGMP_V3_MODE_IS_EXCLUDE:
		case IGMP_V3_CHANGE_TO_EXCLUDE_MODE:
			printf(" / Join group %s, for source%s {\n",inet_ntoa(ip), (group_record.num > 1) ? "s not in" : " not");
			break;
		case IGMP_V3_ALLOW_NEW_SOURCES:
			printf(" / Group %s, new source%s {\n",inet_ntoa(ip), (group_record.num > 1) ? "s" : "");
			break;
		case IGMP_V3_BLOCK_OLD_SOURCES:
			printf(" / Group %s, block source%s {\n",inet_ntoa(ip), (group_record.num >1) ? "s" : "");
			break;
		default:
			printf(" / Group %s, unknown record type (?), sources {\n",inet_ntoa(ip));
			break;
		}
	}

	/* source addresses */
	while(group_record.num --){
		printf("%s%s",inet_ntoa(ip), (group_record.num ? ", ":"}"));

		offset += 4;
	}

}

在工作机制上,与IGMPv2相比,IGMPv3增加了主机对组播源的选择能力。

在这里插入图片描述

IGMPv3的成员报告报文的目的地址为224.0.0.22。通过在报告报文中携带组记录,主机在加入组播组的同时,能够明确要求接收或不接收特定组播源发出的组播数据。

总结

IGMP通信协议有效地将多播数据传输到接收方,因此,不会将垃圾数据包传输到主机,从而显示出最佳性能。连接所有共享链接后,带宽将被完全消耗。主机可以离开多播组并加入另一个。

IGMP有个不好的地方是,它在过滤和安全性方面没有提供良好的效率。由于缺少TCP,可能会发生网络拥塞。IGMP容易受到DOS攻击(拒绝服务)等攻击的攻击。

需要完整源码的可以加我微信。最后,建议大家在学习网络知识的时候,建议看RFC官方文档。

参考:
https://www.rfc-editor.org/rfc/pdfrfc/rfc1054.txt.pdf
https://www.rfc-editor.org/rfc/pdfrfc/rfc1112.txt.pdf
https://www.rfc-editor.org/rfc/pdfrfc/rfc2236.txt.pdf
https://www.rfc-editor.org/rfc/pdfrfc/rfc3376.txt.pdf

欢迎关注微信公众号【程序猿编码】,添加本人微信号(17865354792),欢迎进入技术交流群。我们一起学习进步!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值