bigywater.tech
网络协议 · 34

IGMPv2 与 IGMPv3:报文格式与源过滤

IGMPv3 比 v2 多了什么?“只接收某个源的组播”是怎么表达的?本机的套接字源过滤是真的吗,路由器知道吗?这篇手工构造各种 v2 / v3 报文用 tcpdump 和 scapy 一起解码,再用真实套接字测源过滤。

8 字节 / 16 字节
IGMPv2 报告与 IGMPv3 报告(1 条记录,0 个源)的 IGMP 部分大小
0 个 v3
本机加入 / 屏蔽 / 离开的过程中发出的 IGMPv3 报告个数:线上全是 v2(16 个报文)
只要别的源 → 没收到
IP_ADD_SOURCE_MEMBERSHIP 只要另一个源时,本机内核对来自本机地址的组播的过滤结果

v2 与 v3 的区别

实测 · 本机执行输出
    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.2

v2 的查询和报告都是 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,路由器并不知道本机的源过滤。

分步演示

IGMPv2 与 IGMPv3
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64(Apple 芯片)/ Python 3.14.8 / tcpdump(libpcap)/ scapy 2.8 / dpkt / dnspython / h2 / aioquic / OpenSSL / curl 8.7.1;抓包在 lo0(环回)上做,少数实验用真实网卡 en0(这台机器上 en0 是 Wi-Fi,tcpdump 看到的是系统转成以太网格式的帧,所以文中的“以太网头”都指这个 14 字节的头)或访问公网(会在文章里注明);需要丢包 / 延迟 / 限速时,用一个只作用于 lo0 端口 9400-9499 的 dummynet 辅助脚本(netlab);所有服务端都是本机自己起的进程;时间戳取自 tcpdump 的 -tt,数字每次运行会有小幅抖动。第一至三节是手工构造的报文(文档地址 192.0.2.0/24、198.51.100.0/24),不是抓到的;第四节是真实套接字与 en0 上的抓包,发送方源地址就是本机地址,没有第二台主机,所以只能验证“本机地址”和一个虚构地址两种源
    • 没有验证:IGMPv3 报文在真实网络上的发送(本机在这个网段按 v2 说话);v3 查询的响应延迟与重传(Robustness);IGMP snooping 交换机对源过滤的处理;IP_BLOCK_SOURCE 没有屏蔽本机回送包的原因;SSM(源特定组播,232.0.0.0/8)的行为
    京ICP备2026067502号-1京公网安备11010502063487号