我有MRD6安装在我的树莓派上。它向本地接口 (tun0) 注册并定期通过其传输 MLDv2 查询。
根据 [RFC3810],MLDv2 消息类型是 ICMPv6 消息的子集,并在 IPv6 数据包中通过前面的下一个标头值 58 (0x3a) 进行标识。它们与链路本地 IPv6 源地址、IPv6 跳数限制 1 和 IPv6 路由器警报选项一起发送 [RFC2711] 在逐跳选项标头中。
我可以确认我在 tun0 上定期看到这些数据包:
pi@machine:~ $ sudo tcpdump -i tun0 ip6 -vv -XX
01:22:52.125915 IP6 (flowlabel 0x71df6, hlim 1, next-header Options (0)
payload length: 36)
fe80::69bf:be2d:e087:9921 > ip6-allnodes: HBH (rtalert: 0x0000) (padn)
[icmp6 sum ok] ICMP6, multicast listener query v2 [max resp delay=10000]
[gaddr :: robustness=2 qqi=125]
0x0000: 6007 1df6 0024 0001 fe80 0000 0000 0000 `....$..........
0x0010: 69bf be2d e087 9921 ff02 0000 0000 0000 i..-...!........
0x0020: 0000 0000 0000 0001 3a00 0502 0000 0100 ........:.......
0x0030: 8200 b500 2710 0000 0000 0000 0000 0000 ....'...........
0x0040: 0000 0000 0000 0000 027d 0000 .........}..
我在 tun0 上的应用程序中设置了一个套接字,如下所示,因为我希望这些是 ICMP 数据包:
int fd = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); // ICMP
// ... bind this socket to tun0
int interfaceIndex = // tun0 interface Index
int mcastTTL = 10;
int loopBack = 1;
if (setsockopt(listener->socket,
IPPROTO_IPV6,
IPV6_MULTICAST_IF,
&interfaceIndex,
sizeof(interfaceIndex))
< 0) {
perror("setsockopt:: IPV6_MULTICAST_IF:: ");
}
if (setsockopt(listener->socket,
IPPROTO_IPV6,
IPV6_MULTICAST_LOOP,
&loopBack,
sizeof(loopBack))
< 0) {
perror("setsockopt:: IPV6_MULTICAST_LOOP:: ");
}
if (setsockopt(listener->socket,
IPPROTO_IPV6,
IPV6_MULTICAST_HOPS,
&mcastTTL,
sizeof(mcastTTL))
< 0) {
perror("setsockopt:: IPV6_MULTICAST_HOPS:: ");
}
struct ipv6_mreq mreq6 = {{{{0}}}};
MEMCOPY(&mreq6.ipv6mr_multiaddr.s6_addr, sourceAddress, 16);
mreq6.ipv6mr_interface = interfaceIndex;
if (setsockopt(listener->socket,
IPPROTO_IPV6,
IPV6_JOIN_GROUP,
&mreq6,
sizeof(mreq6))
< 0) {
perror("setsockopt:: IPV6_JOIN_GROUP:: ");
}
通过这种方式设置套接字,我可以接收 ICMP 回显请求、对我自己的地址的回复以及使用链路本地多播地址发送的多播。但是,我没有看到任何MLDv2 查询。
这是我的接收循环:
uint8_t received[1000] = { 0 };
struct sockaddr_storage peerAddress = { 0 };
socklen_t addressLength = sizeof(peerAddress);
socklen_t addressLength = sizeof(peerAddress);
int receivedLength = recvfrom(sockfd,
received,
sizeof(received),
0,
(struct sockaddr *)&peerAddress,
&addressLength);
if (receivedLength > 0) {
// Never get here for MLDv2 queries.
}
进一步研究后,我发现了 IPV6_ROUTER_ALERT 套接字选项,手册页对此进行了如下描述:
IPV6_ROUTER_ALERT
Pass forwarded packets containing a router alert hop-by-hop option to this socket.
Only allowed for SOCK_RAW sockets. The tapped packets are not forwarded by the
kernel, it is the user's responsibility to send them out again. Argument is a
pointer to an integer. A positive integer indicates a router alert option value
to intercept. Packets carrying a router alert option with a value field
containing this integer will be delivered to the socket. A negative integer
disables delivery of packets with router alert options to this socket.
所以我想我错过了这个选项,并尝试如下设置。 [RFC2710] 0 表示组播侦听器发现消息。
int routerAlertOption = 0;
if (setsockopt(listener->socket,
IPPROTO_IPV6,
IPV6_ROUTER_ALERT,
&routerAlertOption,
sizeof(routerAlertOption))
< 0) {
perror("setsockopt:: IPV6_ROUTER_ALERT:: ");
}
但是,这给了我 ENOPROTOOPT 错误(errno 92)。更多谷歌搜索(http://www.atm.tut.fi/list-archive/usagi-users-2005/msg00317.html)让我发现您无法使用 IPPROTO_ICMPV6 协议设置 IPV6_ROUTER_ALERT 选项。它需要使用 IPPROTO_RAW 协议定义的套接字。
但是,将我的套接字定义为:
int fd = socket(AF_INET6, SOCK_RAW, IPPROTO_RAW);
意味着我无法再在recvfrom 中接收任何ICMP 数据包。
TL;DR:如何使用 IPv6 套接字读取 MLDv2 查询?
编辑(答案): 看来 Linux 的传统实现在将 MLDv2 数据包传递到 ICMPV6 套接字时会丢弃它们。这是为什么,我不确定。 (可能是因为下一个标题选项。)
我采用了在 tun0 接口上读取原始数据包的方法。我在这里遵循 ping6_ll.c 示例:http://www.pdbuchan.com/rawsock/rawsock.html。
它使用带有(SOCK_RAW,ETH_P_ALL)的套接字。您还可以设置一些 SOL_PACKET 选项来过滤接口上的特定多播规则。