bigywater.tech
网络协议 · 32

UDP 组播:加入组、TTL 范围与回环

组播和广播有什么不同?加入一个组播组时线上发生了什么?组播的 TTL 为什么默认是 1,环回是怎么回事?这篇在本机 en0 和 lo0 上真的加入一个组,抓 IGMP 报告和组播帧,逐项验证成员关系、TTL 范围、回环和接口的规则。

01:00:5e:7f:2a:63
239.255.42.99 对应的以太网组播 MAC:01:00:5e 加 IP 地址的低 23 位
32 个
共用同一个以太网组播 MAC 的不同 IP 组播地址数量(IP 里有 5 位被丢掉)
TTL 1
组播默认的 IP_MULTICAST_TTL:默认不会离开本网段

加入组

实测 · 本机执行输出
  加入前 netstat -g 里 en0 的 IPv4 组 2 个(没有 239.255.42.99);加入后:['239.255.42.99', '1:0:5e:7f:2a:63', 'en0']
  239.255.42.99 → 以太网组播 MAC = 01:00:5e + 低 23 位 = 01:00:5e:7f:2a:63(第二字节的最高位被清掉,只取 IP 地址后 23 位)
  抓到 IGMP 报文:IP 目的 224.0.0.251,以太网目的 MAC 01:00:5e:00:00:fb,IGMP 类型 0x16(成员报告),组地址 224.0.0.251,IP 头 24 字节(含 Router Alert 选项),TTL 1
  抓到 IGMP 报文:IP 目的 239.255.42.99,以太网目的 MAC 01:00:5e:7f:2a:63,IGMP 类型 0x16(成员报告),组地址 = 239.255.42.99,IP 头 24 字节(含 Router Alert 选项),TTL 1
  抓到 IGMP 报文:IP 目的 224.0.0.251,以太网目的 MAC 01:00:5e:00:00:fb,IGMP 类型 0x16(成员报告),组地址 224.0.0.251,IP 头 24 字节(含 Router Alert 选项),TTL 1
  共 3 个 IGMP 报文;32 个不同的组播 IP 地址会映射到同一个以太网 MAC(IP 地址里有 5 位被丢掉),例如 239.255.42.99 与 224.127.42.99 的 MAC 相同
  验证:224.127.42.99 → 01:00:5e:7f:2a:63,与 239.255.42.99 的 MAC 相同

IP_ADD_MEMBERSHIP 让网卡开始收对应的以太网组播 MAC 的帧,同时内核发出 IGMP 成员报告。netstat -g 能看到成员关系和 MAC。

谁能收

实测 · 本机执行输出
  IP_MULTICAST_LOOP 默认值 1(1 = 本机上加入了组的套接字也会收到自己发的)
  默认(LOOP=1):加入了组的接收方 1 收到;加入了组的接收方 2(同一端口,SO_REUSEPORT)收到;没有加入组、只绑了 0.0.0.0 端口的接收方 没收到
  IP_MULTICAST_LOOP = 0:加入了组的接收方 1 没收到;接收方 2 没收到(同一台机器上的接收方都收不到了;别的机器仍然能收到,这里没有第二台机器可验证)
  没有设置 IP_MULTICAST_IF 的发送方发同一个组:加入了组的接收方 1 收到(出口按路由表选)
  线上:以太网目的 MAC 01:00:5e:7f:2a:63(= 组播 MAC 01:00:5e:7f:2a:63:True),IP 目的 239.255.42.99,TTL 1

只有加入了组的套接字收得到(包括同一台机器上的其它加入者);关掉 IP_MULTICAST_LOOP 后同一台机器上的接收方就收不到自己发的包。

TTL 与范围

实测 · 本机执行输出
  新套接字 IP_MULTICAST_TTL 默认 = 1;设成 1 / 4 / 32 后线上抓到的 TTL:1 → [1];4 → [4];32 → [32]
  传统约定:TTL 0 = 只在本机;1 = 本子网;<32 = 本站点 / 机构内;<64 = 本地区;<128 = 本大洲;<255 = 全球。组播路由器每转发一次 TTL 减 1,所以 TTL 同时是“传播半径”;本机默认 1,所以默认不会离开这个网段
  224.0.0.0/24 是“本地链路”组播(路由器永远不转发),8 秒里抓到别的设备发往它的 IP 包 236 个,TTL 取值 [1, 254, 255]

默认 TTL 是 1,只在本网段;TTL 越大传播半径越大;224.0.0.0/24 永远是本地链路。

按接口

实测 · 本机执行输出
  route get 239.255.42.99:接口 en0,标志 <UP,HOST,DONE,LLINFO,WASCLONED,MULTICAST,IFSCOPE>
  接收方在某个接口地址上加入,发送方用同一个接口地址发:en0 的地址:收到;lo0 的地址 127.0.0.1:收到
  接收方在 lo0 上加入、发送方从 en0 发:没收到——组播成员关系是按接口算的,在哪个接口加入就只收那个接口来的

成员关系属于接口:在 lo0 上加入就只收 lo0 来的组播,发送方用 IP_MULTICAST_IF 选出口。

计数

实测 · 本机执行输出
  一个套接字连续加入 400 个不同的组,全部成功
  (sysctl 里没有 net.inet.ip.maxmemberships 这一项)
  两个套接字加入同一个组 239.255.42.77:netstat 里该组的条目数 1;关掉第一个套接字后 1;关掉第二个后 0——内核对同一个接口上的同一个组只记一个成员,按引用计数,最后一个套接字离开才真正退出

同一个接口上的同一个组只记一条成员关系,按引用计数,最后一个套接字离开才真正退出。

分步演示

UDP 组播
本步的对象

    本文用到的工具与边界

    • 本机 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,数字每次运行会有小幅抖动。组播组使用的是私有管理范围 239.255.0.0/16 里的测试地址;同一台机器上没有第二台主机可以验证跨机器的接收,所以 LOOP=0 的效果只验证了本机内;第三节最后统计的是别人发往 224.0.0.0/24 的包,只记个数与 TTL
    • 没有验证:跨路由器的组播转发(PIM);一个套接字加入的组数量上限(试到 400 个仍没有上限);IGMP 查询触发的重复报告(见下一篇);Wi-Fi 接入点对组播的处理方式;IPv6 组播(MLD)的行为
    京ICP备2026067502号-1京公网安备11010502063487号