简体   繁体   English

带有 static 的套接字选项 IP_MULTICAST_IF 多播路由从多播切换到单播 MAC 寻址

[英]Socket option IP_MULTICAST_IF with static multicast route switches from multicast to unicast MAC addressing

I would like experts' advice on the usage of the socket option IP_MULTICAST_IF ("set multicast interface") combined with static multicast routes.我希望专家对套接字选项 IP_MULTICAST_IF(“设置多播接口”)与 static 多播路由的使用的建议。

On a LAN, a multicast IP datagram is commonly sent in a multicast Ethernet frame (IP/MAC multicast destination address mapping).在 LAN 上,多播 IP 数据报通常在多播以太网帧(IP/MAC 多播目标地址映射)中发送。 On a multi-homed Linux system (kernel 5.11), I have noticed that the socket option IP_MULTICAST_IF modifies the behavior as follow:在多宿主 Linux 系统(内核 5.11)上,我注意到套接字选项IP_MULTICAST_IF将行为修改如下:

  • Without static route, the multicast IP datagram is always sent in a multicast Ethernet frame, with or without IP_MULTICAST_IF .如果没有 static 路由,多播 IP 数据报始终在多播以太网帧中发送,无论是否带有IP_MULTICAST_IF
  • With a static route, without IP_MULTICAST_IF , the multicast IP datagram is sent in a multicast Ethernet frame.使用 static 路由,没有IP_MULTICAST_IF ,多播 IP 数据报在多播以太网帧中发送。
  • With a static route, with IP_MULTICAST_IF , the multicast IP datagram is sent in a unicast Ethernet frame to the gateway.使用 static 路由和IP_MULTICAST_IF ,多播 IP 数据报在单播以太网帧中发送到网关。

First question: With a static route for the multicast packet, should the multicast IP datagram be sent in a multicast Ethernet frame or in a unicast Ethernet frame to the gateway?第一个问题:对于多播数据包的 static 路由,多播 IP 数据报应该以多播以太网帧还是单播以太网帧发送到网关?

Second question: Whatever is the answer to the first question, why does the socket option IP_MULTICAST_IF switch from multicast to unicast MAC addressing?第二个问题:不管第一个问题的答案是什么,为什么套接字选项 IP_MULTICAST_IF 会从多播切换到单播 MAC 寻址?

The Linux man page ("man 7 ip") is not very explicit: Linux 手册页(“man 7 ip”)不是很明确:

IP_MULTICAST_IF (since Linux 1.2)
    Set  the  local device for a multicast socket.  The argument for setsockopt(2) is an ip_mreqn or (since Linux 3.5)
    ip_mreq structure similar to IP_ADD_MEMBERSHIP, or an in_addr structure.  (The kernel determines  which  structure
    is being passed based on the size passed in optlen.)  For getsockopt(2), the argument is an in_addr structure.

Here is a sample configuration to reproduce this between two Linux virtual machines.这是在两个 Linux 虚拟机之间重现此问题的示例配置。

First system, "vmubuntu", receiver running Wireshark on interface ens38:第一个系统“vmubuntu”,在接口 ens38 上运行 Wireshark 的接收器:

ens33: 00:0C:29:46:B7:CE  192.168.98.3   / 24  -> NAT, default route
ens38: 00:50:56:39:F0:03  192.168.233.11 / 24  -> host local vmware

Second system, "vmfedora", sender system:第二个系统,“vmfedora”,发件人系统:

ens33: 00:0C:29:B6:33:8D  192.168.98.2   / 24  -> NAT, default route
ens37: 00:50:56:29:34:37  192.168.233.10 / 24  -> host local vmware

We declare a static route on "vmfedora" for multicast traffic, using "vmubuntu" as gateway.我们在“vmfedora”上声明一个 static 路由用于多播流量,使用“vmubuntu”作为网关。 Note that the default route for general IP traffic is on the other interface.请注意,一般 IP 流量的默认路由在另一个接口上。

$ uname -a
Linux vmfedora 5.11.15-200.fc33.x86_64 #1 SMP Fri Apr 16 13:41:20 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
$
$ sudo route add -net 224.0.0.0 netmask 240.0.0.0 gw 192.168.233.11 dev ens37
$
$ route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         192.168.98.1    0.0.0.0         UG    20100  0        0 ens33
192.168.98.0    0.0.0.0         255.255.255.0   U     100    0        0 ens33
192.168.233.0   0.0.0.0         255.255.255.0   U     101    0        0 ens37
224.0.0.0       192.168.233.11  240.0.0.0       UG    0      0        0 ens37

Let's send multicast packets from "vmfedora" to 239.230.2.44:3044, in the range of the route.让我们将多播数据包从“vmfedora”发送到路由范围内的 239.230.2.44:3044。 We bind the socket to local address 192.168.233.10, which is also the outgoing interface for the static route.我们将套接字绑定到本地地址 192.168.233.10,这也是 static 路由的出接口。 We do not use the socket option IP_MULTICAST_IF (sample code below).我们不使用套接字选项IP_MULTICAST_IF (下面的示例代码)。

On "vmubuntu", Wireshark reports this:在“vmubuntu”上,Wireshark 报告了这一点:

Internet Protocol Version 4, Src: 192.168.233.10, Dst: 239.230.2.44
Ethernet II, Src: VMware_29:34:37 (00:50:56:29:34:37), Dst: IPv4mcast_66:02:2c (01:00:5e:66:02:2c)
    Destination: IPv4mcast_66:02:2c (01:00:5e:66:02:2c)
        Address: IPv4mcast_66:02:2c (01:00:5e:66:02:2c)
        .... ..0. .... .... .... .... = LG bit: Globally unique address (factory default)
        .... ...1 .... .... .... .... = IG bit: Group address (multicast/broadcast)
    Source: VMware_29:34:37 (00:50:56:29:34:37)
        Address: VMware_29:34:37 (00:50:56:29:34:37)
        .... ..0. .... .... .... .... = LG bit: Globally unique address (factory default)
        .... ...0 .... .... .... .... = IG bit: Individual address (unicast)
    Type: IPv4 (0x0800)

We can see that the IP datagram with a multicast destination address is sent in an Ethernet frame with the corresponding multicast destination address.我们可以看到,带有多播目标地址的 IP 数据报是在具有相应多播目标地址的以太网帧中发送的。

Now, let's add socket option IP_MULTICAST_IF after bind() .现在,让我们在bind()之后添加套接字选项IP_MULTICAST_IF Wireshark reports this: Wireshark 报告了这一点:

Internet Protocol Version 4, Src: 192.168.233.10, Dst: 239.230.2.44
Ethernet II, Src: VMware_29:34:37 (00:50:56:29:34:37), Dst: VMware_39:f0:03 (00:50:56:39:f0:03)
    Destination: VMware_39:f0:03 (00:50:56:39:f0:03)
        Address: VMware_39:f0:03 (00:50:56:39:f0:03)
        .... ..0. .... .... .... .... = LG bit: Globally unique address (factory default)
        .... ...0 .... .... .... .... = IG bit: Individual address (unicast)
    Source: VMware_29:34:37 (00:50:56:29:34:37)
        Address: VMware_29:34:37 (00:50:56:29:34:37)
        .... ..0. .... .... .... .... = LG bit: Globally unique address (factory default)
        .... ...0 .... .... .... .... = IG bit: Individual address (unicast)
    Type: IPv4 (0x0800)

Now, we see that the IP datagram with a multicast destination address is sent in an Ethernet frame with the unicast MAC address of the gateway as destination address.现在,我们看到带有多播目标地址的 IP 数据报在以网关的单播 MAC 地址作为目标地址的以太网帧中发送。

And the only difference is the socket option IP_MULTICAST_IF .唯一的区别是套接字选项IP_MULTICAST_IF

If you want to reproduce this, see the code below.如果您想重现此内容,请参阅下面的代码。 It takes one mandatory command line parameter: the IP address of the local interface to which the socket is bound.它需要一个强制性的命令行参数:套接字绑定到的本地接口的 IP 地址。 The second optional command line parameter is "-f" to force the socket option IP_MULTICAST_IF (not set by default).第二个可选的命令行参数是“-f”,用于强制套接字选项IP_MULTICAST_IF (默认情况下未设置)。

Thanks for your explanations on this option.感谢您对此选项的解释。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

int main(int argc, char* argv[])
{
    struct sockaddr_in dest;
    dest.sin_family = AF_INET;
    dest.sin_port = htons(3044);
    inet_pton(AF_INET, "239.230.2.44", &dest.sin_addr);

    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_port = 0;
    local.sin_addr.s_addr = 0;

    int use_mcast_if = 0;

    for (int arg = 1; arg < argc; ++arg) {
        if (strcmp(argv[arg], "-f") == 0) {
            use_mcast_if = 1;
        }
        else if (inet_pton(AF_INET, argv[arg], &local.sin_addr) != 1) {
            fprintf(stderr, "invalid local address: %s\n", argv[arg]);
            return EXIT_FAILURE;
        }
    }

    const int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (sock < 0) {
        perror("socket()");
        return EXIT_FAILURE;
    }
    
    if (bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0) {
        perror("bind()");
        return EXIT_FAILURE;
    }

    if (use_mcast_if && setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, &local.sin_addr, sizeof(local.sin_addr)) < 0) {
        perror("setsockopt(IP_MULTICAST_IF)");
        return EXIT_FAILURE;
    }

    const int data = 0x12345678;
    for (int i = 0; i < 10; ++i) {
        sendto(sock, &data, sizeof(data), 0, (struct sockaddr*)&dest, sizeof(dest));
    }

    close(sock);
    return EXIT_SUCCESS;
}

Linux doesn't normally handle multicast routing without special software such as mrouted or pimd. Linux 通常不会在没有特殊软件(例如 mrouted 或 pimd)的情况下处理多播路由。 I tried this on a CentOS 7 VM (3.10 kernel) and saw unicast MAC addresses with the static route whether or not I used IP_MULTICAST_IF .我在 CentOS 7 VM(3.10 内核)上尝试了这个,并看到了 static 路由的单播 MAC 地址,无论我是否使用IP_MULTICAST_IF

The following post on ServerFault goes into this in more detail:以下有关 ServerFault 的帖子对此进行了更详细的介绍:

https://serverfault.com/questions/814259/use-ip-route-add-to-add-multicast-routes-to-multiple-interfaces https://serverfault.com/questions/814259/use-ip-route-add-to-add-multicast-routes-to-multiple-interfaces

TL;DR -- Don't use static multicast routes. TL;DR -- 不要使用 static 多播路由。 Stick with setting IP_MULTICAST_IF .坚持设置IP_MULTICAST_IF

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM