IGMPv3 比 v2 多了什么?“只接收某个源的组播”是怎么表达的?本机的套接字源过滤是真的吗,路由器知道吗?这篇手工构造各种 v2 / v3 报文用 tcpdump 和 scapy 一起解码,再用真实套接字测源过滤。
v2 通用查询(最大响应 10 秒) IGMP 部分 8 字节,校验和 0xee9b(验算 通过),整个 IP 包 32 字节
v3 通用查询 IGMP 部分 12 字节,校验和 0xec1e(验算 通过),整个 IP 包 36 字节
v3 组-源查询(2 个源) IGMP 部分 20 字节,校验和 0x7d42(验算 通过),整个 IP 包 44 字节
v2 成员报告 IGMP 部分 8 字节,校验和 0xcf9c(验算 通过),整个 IP 包 32 字节
v3 报告(1 条记录:EXCLUDE 空 = 加入整个组) IGMP 部分 16 字节,校验和 0xc19b(验算 通过),整个 IP 包 40 字节
v3 报告(2 条记录:INCLUDE{S1,S2} + EXCLUDE{})IGMP 部分 32 字节,校验和 0x51bd(验算 通过),整个 IP 包 56 字节
v2 查询的“最大响应时间”字段是 1 字节、单位 0.1 秒(最大 25.5 秒);v3 的 8 位字段 ≥ 128 时用浮点格式:例如要表示 200 秒 = 2000 个 0.1 秒,编码成 0xc0,解回来是 2048
v1 / v2 / v3 查询的区分:IGMP 部分长度 = 8 且最大响应时间 = 0 → v1;长度 = 8 且 ≠ 0 → v2;长度 ≥ 12 → v3
v3 报告发往 224.0.0.22(所有 IGMPv3 路由器),v2 报告发往被报告的组地址本身,v2 离开发往 224.0.0.2v2 的查询和报告都是 8 字节、针对单个组;v3 报告可以带多条记录,每条记录有类型和源地址列表;v3 查询能带源列表,长度 ≥ 12 字节。
tcpdump: 08:00:01.000000 IP (tos 0xc0, ttl 1, id 0, offset 0, flags [none], proto IGMP (2), length 32, options (RA))
tcpdump: 192.0.2.1 > 224.0.0.1: igmp query v2
tcpdump: 08:00:01.001000 IP (tos 0xc0, ttl 1, id 0, offset 0, flags [none], proto IGMP (2), length 36, options (RA))
tcpdump: 192.0.2.1 > 224.0.0.1: igmp query v3
tcpdump: 08:00:01.002000 IP (tos 0xc0, ttl 1, id 0, offset 0, flags [none], proto IGMP (2), length 44, options (RA))
tcpdump: 192.0.2.1 > 239.255.42.99: igmp query v3 [gaddr 239.255.42.99 { 198.51.100.7 198.51.100.8 }]
tcpdump: 08:00:01.003000 IP (tos 0xc0, ttl 1, id 0, offset 0, flags [none], proto IGMP (2), length 32, options (RA))
tcpdump: 192.0.2.10 > 239.255.42.99: igmp v2 report 239.255.42.99
tcpdump: 08:00:01.004000 IP (tos 0xc0, ttl 1, id 0, offset 0, flags [none], proto IGMP (2), length 40, options (RA))
tcpdump: 192.0.2.10 > 224.0.0.22: igmp v3 report, 1 group record(s) [gaddr 239.255.42.99 is_ex { }]
tcpdump: 08:00:01.005000 IP (tos 0xc0, ttl 1, id 0, offset 0, flags [none], proto IGMP (2), length 56, options (RA))
tcpdump: 192.0.2.10 > 224.0.0.22: igmp v3 report, 2 group record(s) [gaddr 239.255.42.99 is_in { 198.51.100.7 198.51.100.8 }] [gaddr 239.255.42.100 is_ex { }]
scapy 对这 6 个 IP 包解出的上层类型:IGMP_MQ / IGMPv3_MQ / IGMPv3_MQ / IGMPv2_MR / IGMPv3_MR / IGMPv3_MR
scapy 解出最后一个 v3 报告:记录数 2,记录类型 [1, 2],组 ['239.255.42.99', '239.255.42.100'],源 [['198.51.100.7', '198.51.100.8'], []]看查询的 IGMP 部分长度:8 字节且响应时间为 0 是 v1,8 字节且不为 0 是 v2,≥ 12 字节是 v3。
IP_ADD_MEMBERSHIP(任意源,加入) → v3 报告里的记录:状态变化 CHANGE_TO_EXCLUDE{}(= 接收任何源)
IP_DROP_MEMBERSHIP(离开) → v3 报告里的记录:状态变化 CHANGE_TO_INCLUDE{}(= 不接收任何源 = 离开)
IP_ADD_SOURCE_MEMBERSHIP(加入时只要某个源) → v3 报告里的记录:状态变化 CHANGE_TO_INCLUDE{S}
再加一个源 S2 → v3 报告里的记录:ALLOW_NEW_SOURCES{S2}
IP_BLOCK_SOURCE(任意源模式下屏蔽 S) → v3 报告里的记录:BLOCK_OLD_SOURCES{S} 或 CHANGE_TO_EXCLUDE{S}v3 报告用 MODE_IS_* 报告当前状态,用 CHANGE_TO_* 和 ALLOW / BLOCK 报告变化,没有专门的 Leave。这些映射没有在线上验证,因为本机只发 v2。
每种过滤方式用独立的套接字、独立的端口依次测试。发送方源地址 = 本机 en0 地址,发一个数据报到该组;结果:
不过滤(任意源):收到
只要本机地址这个源:收到
只要另一个源(198.51.100.7):没收到
任意源加入后屏蔽本机地址这个源:收到
任意源加入后屏蔽另一个源:收到
这些加入 / 屏蔽 / 离开的过程中线上的 IGMP 报文共 16 个,类型分布:0x16 × 11;0x17 × 5(0x16 = v2 报告,0x17 = v2 离开,0x22 = v3 报告)
这个网段上的查询者是 v2(见上一篇),本机对它按 v2 说话:v2 报文里没有“源”的概念,过滤信息只存在于本机内核里,并没有告诉路由器本机内核真的会按源地址过滤(只要别的源就收不到);屏蔽自己这个源的实验没有生效,原因没查。因为线上是 v2,路由器并不知道本机的源过滤。