使用IPv6套接字读取MLDv2查询

时间:2017-11-10 14:32:59

标签: sockets ipv6 icmp multicastsocket

我在树莓派上安装了mrd6。它向本地接口(tun0)注册,并定期通过它发送MLDv2查询。

根据[RFC3810],MLDv2消息类型是ICMPv6消息的子集,并通过前面的Next Header值58(0x3a)在IPv6数据包中标识。它们通过链路本地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错误(错误92)。更多Googling(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套接字时丢弃它们。为什么会这样,我不确定。 (可能是因为next-header选项。)

我按照下面接受的答案采用了在tun0接口上读取原始数据包的方法。我在这里跟着ping6_ll.c示例:http://www.pdbuchan.com/rawsock/rawsock.html

它使用带有(SOCK_RAW,ETH_P_ALL)的套接字。您还可以设置一些SOL_PACKET选项来过滤接口上的特定多播规则。

1 个答案:

答案 0 :(得分:1)

通过快速浏览一下RFC,看起来并不好看。每RFC4443 (ICMPv6) 2.4

  

2.4。消息处理规则

     

实现处理时必须遵守以下规则      ICMPv6消息(来自[RFC-1122]):

     

(b)如果收到未知类型的ICMPv6信息性消息,   它必须被默默地丢弃。

根据MLDv2规范,它使用类型130,143,可能是其他东西(在RFC中没有看到更多的图表),而有效的ICMPv6类型是1,2,3,4,101,107,127,128, 129,200,201,255。

如果要将MLDv2数据包传递给ICMPv6套接字,则实现(内核)必须丢弃它们。就个人而言,如果传统的实施方案无论如何都会丢弃数据包,我认为让MLDv2看起来像ICMPv6并不是很有意义,但我没有看到任何与此声明相矛盾的内容。

你肯定可以更深入地使用原始套接字,特别是考虑到你的堆栈没有识别MLDv2(可能还有一个内核补丁来修复它?)。但是,您必须自己解析IP和ICMP标头。