概要
目前天翼云已在部分资源池支持了组播的能力。有需要组播业务上云,可以通过联系天翼云来开通组播功能。本文基于天翼云的云内组播产品和scapy来学习和实践IGMP协议。
前提条件
-
需要在天翼云上支持组播的资源池上准备一个VPC。
-
在VPC内创建至少一个linux云主机。
-
云主机的python版本要至少3.7.x
scapy
安装过程(可通过scapy官网):
例如通过包管理工具安装
$ pip3 install scapy
scapy使用
使用scapy进入云主机,通过python解释器,或直接进入scapy解释器来构造报文,本文介绍构造方法,直接使用scapy解释器,在linux终端下输入scapy
即可:
# scapy
INFO: Can't import PyX. Won't be able to use psdump() or pdfdump().
WARNING: IPython not available. Using standard Python shell instead.
>>>
在天翼云控制台创建组播域
天翼云的组播产品,目前一个vpc能够创建一个组播域,一个组播域下可以有若干组播组(具体配额和联系天翼云官方查询)
天翼云的组播域包括云内和云间两种来源,云间组播域需要业务侧拉取专线,并对vpc创建专线实例,云间组播域需要联系天翼云官方开通。而云内组播域,组播源使用的也是vpc的云主机,若验证天翼云的组播功能,可以使用域内组播来做实验。本文通过云内组播即可。
云内组播域包含动态加入和静态加入,静态加入不涉及IGMP协议,是通过静态导入云主机来实现的。本文是来验证IGMP协议,所以使用动态加入即可。
创建组播域
要创建组播域,可在网络控制台找到组播,点击添加组播域。填写必要信息后接口创建完成,在这里我们选择动态加入,默认情况下,会选择IGMPv2的协议。下文分别对IGMPv2/IGMPv3来进行实践。
创建动态的组播域后,组播域内不会有任何组播组的,点击组播域的组播转发展示可以看到是没有任何组播组的
需要通过VPC内的云主机通过组播成员关系报文来动态的加组或者离组,我们接下来就使用SCAPY来验证这个过程
IGMPv2
IGMPv2的报文头的格式如下,我们通过scapy来构造的报文也要遵从RFC:
0 1 2 3
0 1 2 3 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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Max Resp Time | Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Group Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
我们关注报文中的Type和Group Address 两个字段即可。Max Resp Time 只在查询报文时有意义,在成员加组离组时,通常配置为0
Type
对于Type,我们只关注IGMPv2,不考虑兼容IGMPv1时,需要了解三种type:
-
0x11 查询报文类型,通常由组播路由器来触发,查询组播成员。在天翼云的动态组播中,也会由特定的组件来发送查询报文。
-
0x16 成员关系报告,由成员来发送报告来加入组播组。
-
0x17 离组报文,成员通过发送离组报文,离开组播组。
构建成员关系报告报文
scapy在构建IGMPv2的组播时,使用的模块是scapy.contrib.igmp
,我们需要引入此模块,除了igmp协议外,我们还需要封装其他网络层,所以需要引入其他scapy模块
>>> from scapy.contrib.igmp import *
>>> from scapy.all import *
构造IGMPv2的加组报文,IP层,需要指定目的地址为要加入的组播地址,源地址指定为本机IP地址,此时要换算type,换算成10进制的整数。例如0x16换算后为22
>>> int(0x16)
22
构造后的报文如下:
IP(src="10.4.0.3", dst="224.3.0.1") / IGMP(type=22, gaddr="224.3.0.1")
可通过对报文的show()方法查看报文的简单格式例如:
>>> p=IP(src="10.4.0.3", dst="224.3.0.1") / IGMP(type=22, gaddr="224.3.0.1")
>>> p.show()
###[ IP ]###
version = 4
ihl = None
tos = 0x0
len = None
id = 1
flags =
frag = 0
ttl = 1
proto = igmp
chksum = None
src = 10.4.0.3
dst = 224.3.0.1
\options \
###[ IGMP ]###
type = Version 2 - Membership Report
mrcode = 20
chksum = None
gaddr = 224.3.0.1
也可以添加以太头,便于查看整个帧的格式,scapy会自动计算mac地址:
>>> q=Ether()/p
>>> q.show()
###[ Ethernet ]###
dst = 01:00:5e:03:00:01
src = fa:16:3e:3b:38:91
type = IPv4
###[ IP ]###
version = 4
ihl = None
tos = 0x0
len = None
id = 1
flags =
frag = 0
ttl = 1
proto = igmp
chksum = None
src = 10.4.0.3
dst = 224.3.0.1
\options \
###[ IGMP ]###
type = Version 2 - Membership Report
mrcode = 20
chksum = None
gaddr = 224.3.0.1
发送成员关系报告报文并通过tcpdump跟踪报文过程
通过tcpdump在云主机中持续抓包(也可以抓取后,通过wireshark等工具分析报文)
# tcpdump -nvveeppi eth0 igmp
发送组播关系报告
>>> sendp(q)
.
Sent 1 packets.
抓到的报文如下:
19:31:55.188578 fa:16:3e:3b:38:91 > 01:00:5e:03:00:01, ethertype IPv4 (0x0800), length 42: (tos 0x0, ttl 1, id 1, offset 0, flags [none], proto IGMP (2), length 28)
10.4.0.3 > 224.3.0.1: igmp v2 report 224.3.0.1
0x0000: 0100 5e03 0001 fa16 3e3b 3891 0800 4500 ..^.....>;8...E.
0x0010: 001c 0001 0000 0102 cfd4 0a04 0003 e003 ................
0x0020: 0001 1614 09e7 e003 0001
在IGMPv2类型下,天翼云组播会周期性发送general query。抓包如下:
19:35:04.232587 fa:16:3e:04:00:01 > 01:00:5e:00:00:01, ethertype IPv4 (0x0800), length 46: (tos 0x0, ttl 1, id 0, offset 0, flags [none], proto IGMP (2), length 32, options (RA))
10.4.0.1 > 224.0.0.1: igmp query v2
0x0000: 0100 5e00 0001 fa16 3e04 0001 0800 4600 ..^.....>.....F.
0x0010: 0020 0000 0000 0102 3ad2 0a04 0001 e000 ........:.......
0x0020: 0001 9404 0000 1164 ee9b 0000 0000 .......d......
此时在组播控制台,点击组播域的组播转发展示,可以看到出现了组播组,能够展示动态组的详细信息:
-
组播地址
-
组播成员
构建离组报文
构造IGMPv2的离组报文,IP层,需要指定目的地址为224.0.0.2,此地址为所有组播路由器的地址,源地址指定为本机IP地址,此时要换算type,换算成10进制的整数。例如0x17换算后为23
在使用中,天翼云组播对IP层的目的地址并不会强制校验224.0.0.2,例如使用要离组的组播地址也可以。
>>> int(0x17)
23
构造后的报文如下:
IP(src="10.4.0.3", dst="224.0.0.2") / IGMP(type=23, gaddr="224.3.0.1")
可通过对报文的show()方法查看报文的简单格式例如:
>>> p=IP(src="10.4.0.3", dst="224.0.0.2") / IGMP(type=23, gaddr="224.3.0.1")
>>> p.show()
###[ IP ]###
version = 4
ihl = None
tos = 0x0
len = None
id = 1
flags =
frag = 0
ttl = 1
proto = igmp
chksum = None
src = 10.4.0.3
dst = 224.0.0.2
\options \
###[ IGMP ]###
type = Leave Group
mrcode = 20
chksum = None
gaddr = 224.3.0.1
也可以添加以太头,便于查看整个帧的格式,scapy会自动计算mac地址:
>>> q=Ether()/p
>>> q.show()
###[ Ethernet ]###
dst = 01:00:5e:00:00:02
src = fa:16:3e:3b:38:91
type = IPv4
###[ IP ]###
version = 4
ihl = None
tos = 0x0
len = None
id = 1
flags =
frag = 0
ttl = 1
proto = igmp
chksum = None
src = 10.4.0.3
dst = 224.0.0.2
\options \
###[ IGMP ]###
type = Leave Group
mrcode = 20
chksum = None
gaddr = 224.3.0.1
发送离组报文并通过tcpdump跟踪报文过程
发送组播关系报告
>>> sendp(q)
.
Sent 1 packets.
抓到的报文如下:
19:44:30.610564 fa:16:3e:3b:38:91 > 01:00:5e:00:00:02, ethertype IPv4 (0x0800), length 42: (tos 0x0, ttl 1, id 1, offset 0, flags [none], proto IGMP (2), length 28)
10.4.0.3 > 224.0.0.2: igmp leave 224.3.0.1
0x0000: 0100 5e00 0002 fa16 3e3b 3891 0800 4500 ..^.....>;8...E.
0x0010: 001c 0001 0000 0102 cfd6 0a04 0003 e000 ................
0x0020: 0002 1714 08e7 e003 0001
此时在组播控制台,点击组播域的组播转发展示,可以看到组播组已经自动删除(由于只有一个组播成员,所以作为最后一个组成员,离组后,整个组就会删除)。
IGMPv3
对于IGMPv3 报文,消息类型减少到了2个,查询报文和成员关系报告报文,而在成员关系报告报文中,会携带多个组记录字段,每个组记录字段单独配置记录类型来控制加组和离组,以及更多的功能
Type
-
0x11 查询报文类型,通常由组播路由器来触发,查询组播成员。在天翼云的动态组播中,也会由特定的组件来发送查询报文。
-
0x22 成员关系报告。
构建成员关系报告报文
报文格式如下,IGMP头中主要字段下放到了各自的组记录中:
0 1 2 3
0 1 2 3 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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type = 0x22 | Reserved | Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Reserved | Number of Group Records (M) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
. .
. Group Record [1] .
. .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
. .
. Group Record [2] .
. .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| . |
. . .
| . |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
. .
. Group Record [M] .
. .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
scapy在构建IGMPv3的组播时,使用的模块是scapy.contrib.igmpv3
,我们需要引入此模块,除了igmp协议外,我们还需要封装其他网络层,所以需要引入其他scapy模块
>>> from scapy.contrib.igmpv3 import *
>>> from scapy.all import *
构造IGMPv3的成员关系报告报文,IP层,需要指定目的地址,RFC要求为`224.0.0.22,源地址指定为本机IP地址。构造后的基本的报文如下,其中IGMPv3mr为字段'Number of Group Records',在本文中,只指定一个即可:
IP(src="10.4.0.3", dst="224.0.0.22") / IGMP() / IGMPv3mr(numgrp=1)
接下来看下组记录的报文格式,IGMPv3为了支持 SSM 模型,在报文中能够携带组播源信息,使组播成员能指定源加入组播组:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Record Type | Aux Data Len | Number of Sources (N) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Multicast Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Address [1] |
+- -+
| Source Address [2] |
+- -+
. . .
. . .
. . .
+- -+
| Source Address [N] |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
. .
. Auxiliary Data .
. .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
而通过记录类型和指定的源地址,可以组合多种加入,离开组播组的报文格式
Record Type
类别 | 记录类型 | 说明 |
当前状态记录(Current-State Record):用来响应查询报文。成员用来通告自己当前的状态。 |
MODE_IS_INCLUDE |
此组播在过滤器会被指定为INCLUDE模式,且组中的源地址 [i] 字段记录包含接口可接受的组播源。 如果源列表是空的,不会加组,并会触发离组 |
MODE_IS_EXCLUDE |
此组播会在过滤器会被指定为EXCLUDE模式,且组中的源地址 [i] 字段记录包含接口不可接受的源列表。 如果源列表是空的,说明可通配接受所有的源,这个报文会触发加组 |
|
过滤器模式更改记录(Filter-Mode-Change Record):成员通过这个类型的报文来修改对应的过滤器模式 | CHANGE_TO_INCLUDE_MODE |
如果原来过滤器中成员对组播组和组播源的对应关系,例如如果 携带的源IP和原EXCLUDE的源IP相同,会直接改变源IP和组的对应关系,变成INCLUDE。 如果源列表是空的,说明,不允许任何源IP,会触发离组 |
CHANGE_TO_EXCLUDE_MODE |
如果原来过滤器中成员对组播组和组播源的对应关系,例如如果 携带的源IP和原INCLUDE的源IP相同,会直接改变源IP和组的对应关系,变成EXCLUDE。 如果源列表是空的,说明,不会过滤任何源IP,会通配所有地址 |
|
源-列表-更改记录(Source-List-Change Record):成员通过这个类型的报文不能修改过滤器的状态,但如何类型和过滤器模式不同,但是源ip重合时,可以对冲源ip。 | ALLOW_NEW_SOURCES | 匹配的过滤模式为INCLUDE,携带的源IP是期待接收到这些源的组播数据包。所以针对INCLUDE的过滤器状态,增加这些源地址。如果是EXCLUDE的状态组记录,则会从列表中删除这些地址,但是不会改变EXCLUDE的状态。所以如果最后对冲了所有的源ip,导致源ip为空,等效于通配允许所有的源地址。 |
BLOCK_OLD_SOURCES | 匹配的过滤模式为EXCLUDE,携带的源IP是拒绝接收从这些源发出的组播数据包。所以针对EXCLUDE的过滤器状态,在列表中增加这些源地址。如果是INCLUDE的状态组记录,则会从列表中删除这些地址,但是不会改变EXCLUDE的状态。所以如果最后对冲了所有的源ip,导致源ip为空,等效于离组。 |
构造报文
本文仅以MODE_IS_INCLUDE和MODE_IS_EXCLUDE作为示例。
构造单个组记录
通过scapy封装的MODE_IS_INCLUDE的报文如下,例如一个组记录,源IP列表由srcaddrs来指定,组播地址由maddr指定:
IP(src="10.4.0.3", dst="224.0.0.22") / IGMPv3() / IGMPv3mr(numgrp=1) / IGMPv3gr(rtype=1, maddr="238.0.3.1", srcaddrs=["10.4.1.3", "10.4.2.3"])
可通过对报文的show()方法查看报文的简单格式例如:
>>> q=Ether()/IP(src="10.4.0.3", dst="224.0.0.22") / IGMPv3() / IGMPv3mr(numgrp=1) / IGMPv3gr(rtype=1, maddr="238.0.3.1", srcaddrs=["10.4.1.3", "10.4.2.3"])
>>> q.show()
###[ Ethernet ]###
dst = 01:00:5e:00:00:16
src = fa:16:3e:3b:38:91
type = IPv4
###[ IP ]###
version = 4
ihl = None
tos = 0xc0
len = None
id = 1
flags =
frag = 0
ttl = 1
proto = igmp
chksum = None
src = 10.4.0.3
dst = 224.0.0.22
\options \
###[ IGMPv3 ]###
type = Version 3 Membership Report
mrcode = 0
chksum = None
###[ IGMPv3mr ]###
res2 = 0x0
numgrp = 1
\records \
###[ IGMPv3gr ]###
rtype = Mode Is Include
auxdlen = 0
numsrc = None
maddr = 238.0.3.1
srcaddrs = [10.4.1.3, 10.4.2.3]
发送组播关系报告
>>> sendp(q)
.
Sent 1 packets.
抓到的报文如下:
21:28:03.237761 fa:16:3e:3b:38:91 > 01:00:5e:00:00:16, ethertype IPv4 (0x0800), length 58: (tos 0xc0, ttl 1, id 1, offset 0, flags [none], proto IGMP (2), length 44)
10.4.0.3 > 224.0.0.22: igmp v3 report, 1 group record(s) [gaddr 238.0.3.1 is_in { 10.4.1.3 10.4.2.3 }]
0x0000: 0100 5e00 0016 fa16 3e3b 3891 0800 45c0 ..^.....>;8...E.
0x0010: 002c 0001 0000 0102 cef2 0a04 0003 e000 .,..............
0x0020: 0016 2200 d4ec 0000 0001 0100 0002 ee00 ..".............
0x0030: 0301 0a04 0103 0a04 0203 ..........
另天翼云默认的query报文只由general query。不会发送特定query报文,抓包如下:
21:30:04.659276 fa:16:3e:04:00:01 > 01:00:5e:00:00:01, ethertype IPv4 (0x0800), length 50: (tos 0x0, ttl 1, id 0, offset 0, flags [none], proto IGMP (2), length 36, options (RA))
10.4.0.1 > 224.0.0.1: igmp query v3
0x0000: 0100 5e00 0001 fa16 3e04 0001 0800 4600 ..^.....>.....F.
0x0010: 0024 0000 0000 0102 3ace 0a04 0001 e000 .$......:.......
0x0020: 0001 9404 0000 1164 ee9b 0000 0000 0000 .......d........
0x0030: 0000
构造多个组记录
添加多个组记录,一个MODE_IS_INCLUDE,一个MODE_IS_EXCLUDE如下:
IP(src="10.4.0.3", dst="224.0.0.22") / IGMPv3() / IGMPv3mr(numgrp=2) / IGMPv3gr(rtype=1, maddr="238.0.3.1", srcaddrs=["10.4.1.3", "10.4.2.3"]) / IGMPv3gr(rtype=2, maddr="238.0.3.2", srcaddrs=["10.4.1.3", "10.4.2.3"])
可通过对报文的show()方法查看报文的简单格式例如:
>>> q=Ether()/IP(src="10.4.0.3", dst="224.0.0.22") / IGMPv3() / IGMPv3mr(numgrp=2) / IGMPv3gr(rtype=1, maddr="238.0.3.1", srcaddrs=["10.4.1.3", "10.4.2.3"]) / IGMPv3gr(rtype=2, maddr="238.0.3.2", srcaddrs=["10.4.1.3", "10.4.2.3"])
>>> q.show()
###[ Ethernet ]###
dst = 01:00:5e:00:00:16
src = fa:16:3e:3b:38:91
type = IPv4
###[ IP ]###
version = 4
ihl = None
tos = 0xc0
len = None
id = 1
flags =
frag = 0
ttl = 1
proto = igmp
chksum = None
src = 10.4.0.3
dst = 224.0.0.22
\options \
###[ IGMPv3 ]###
type = Version 3 Membership Report
mrcode = 0
chksum = None
###[ IGMPv3mr ]###
res2 = 0x0
numgrp = 2
\records \
###[ IGMPv3gr ]###
rtype = Mode Is Include
auxdlen = 0
numsrc = None
maddr = 238.0.3.1
srcaddrs = [10.4.1.3, 10.4.2.3]
###[ IGMPv3gr ]###
rtype = Mode Is Exclude
auxdlen = 0
numsrc = None
maddr = 238.0.3.2
srcaddrs = [10.4.1.3, 10.4.2.3]
发送组播关系报告
>>> sendp(q)
.
Sent 1 packets.
抓到的报文如下:
21:31:42.853986 fa:16:3e:3b:38:91 > 01:00:5e:00:00:16, ethertype IPv4 (0x0800), length 74: (tos 0xc0, ttl 1, id 1, offset 0, flags [none], proto IGMP (2), length 60)
10.4.0.3 > 224.0.0.22: igmp v3 report, 2 group record(s) [gaddr 238.0.3.1 is_in { 10.4.1.3 10.4.2.3 }] [gaddr 238.0.3.2 is_ex { 10.4.1.3 10.4.2.3 }]
0x0000: 0100 5e00 0016 fa16 3e3b 3891 0800 45c0 ..^.....>;8...E.
0x0010: 003c 0001 0000 0102 cee2 0a04 0003 e000 .<..............
0x0020: 0016 2200 cad8 0000 0002 0100 0002 ee00 ..".............
0x0030: 0301 0a04 0103 0a04 0203 0200 0002 ee00 ................
0x0040: 0302 0a04 0103 0a04 0203
此时在组播控制台,点击组播域的组播转发展示,可以看到出现了组播组,能够展示动态组的详细信息:
-
组播地址
-
组播成员
通过构造成INCLUDE的模式,并源列表为空时,会触发离组
那么由下列几种方式
-
在任意过滤器模式下,构造MODE_IS_INCLUDE,源列表为空。
-
在任意过滤器模式下,构造CHANGE_TO_INCLUDE_MODE,源列表为空。
-
在INCLUDE过滤器模式下,构造BLOCK_OLD_SOURCES,源列表和当前组对应成员的源列表相同。
在上述几种方式离组后,此时在组播控制台,点击组播域的组播转发展示,可以看到组播组已经自动删除(由于只有一个组播成员,所以作为最后一个组成员,离组后,整个组就会删除)。