【问题标题】:IPv6 multicast interface selectionIPv6组播接口选择
【发布时间】:2018-09-05 05:53:06
【问题描述】:

setsockopt 为 IPv4 的传出流量选择接口的方法是 IP_MULTICAST_IF,它接受两个参数。来自ip(4) 手册页:

为多播套接字设置本地设备。论据 setsockopt(2)ip_mreqn 或(自 Linux 3.5 起)ip_mreq 类似于IP_ADD_MEMBERSHIPin_addr 结构的结构。

当尝试对 IPv6 流量执行类似操作时,该选项会更改为接口索引。来自ipv6(4) 手册页:

在套接字上设置用于传出多播数据包的设备。 这仅适用于SOCK_DGRAMSOCK_RAW 套接字。这 参数是一个指向接口索引的指针(参见netdevice(7)) 整数。

如果网络接口(例如eth0)分配有多个地址,会发生什么情况?是不是 IPv6 的 socket 接口去掉了单独使用每个地址的可能性?

【问题讨论】:

  • 因此在发布的情况下添加成员资格是不相关的,这是 IP_MULTICAST_IF 在这种情况下解决的问题。我的问题是:给定一个具有多个地址的接口,在 IPv4 中,您可以在特定地址上发布。鉴于 IPv6,此功能似乎不存在:为什么?有没有办法获得功能?

标签: c linux sockets ipv6 multicast


【解决方案1】:

如果您的接口只有一个链路本地地址 (fe80::/10) 和一个可公开路由的地址,则传出数据包的源地址取决于您要发送到的多播地址的范围。

IPv6 多播地址的格式为ffxy::/16,其中x 是标志字段,y 是范围。如果范围是 1(本地接口)或 2(本地链接),则源地址将是链接本地地址。如果范围为 3 或更高,则源地址将是可公开路由的地址。

另一方面,如果您的接口有多个可公开路由的地址,您需要在发送数据报时使用sendmsg,以便您可以使用IPV6_PKTINFO 控制标头设置源地址。

下面是一个完整的示例,说明如何执行此操作,假设您在一个接口上有 2001::1:2:32002::1:2:3 作为 IPv6 地址,ff03::1:2:3 是您发送到的多播地址。

#define _GNU_SOURCE   // needed for some IPv6 datatypes to be visible

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

// multicast address to send to
const char *maddr = "ff03::1:2:3";

// uncomment the line for the source address you want to use
const char *srcaddr = "2001::1:2:3";
//const char *srcaddr = "2002::1:2:3";

int main()
{
    int sock;
    struct sockaddr_in6 dstaddr;

    struct iovec iovec[1];
    struct msghdr msg;
    struct cmsghdr* cmsg;
    char msg_control[1024];
    char udp_packet[] = "this is a test";
    int cmsg_space;
    struct in6_pktinfo *pktinfo;

    dstaddr.sin6_family = AF_INET6;
    inet_pton(AF_INET6, maddr, &dstaddr.sin6_addr);
    dstaddr.sin6_port = htons(5555);
    dstaddr.sin6_flowinfo = 0;
    dstaddr.sin6_scope_id = 0;

    if ((sock=socket(AF_INET6, SOCK_DGRAM, 0)) == -1) {
        perror("socket failed");
        exit(1);
    }

    // set up the msghdr structure with the destination address, 
    // buffer to send, and control info buffer
    iovec[0].iov_base = udp_packet;
    iovec[0].iov_len = strlen(udp_packet);
    msg.msg_name = &dstaddr;
    msg.msg_namelen = sizeof(dstaddr);
    msg.msg_iov = iovec;
    msg.msg_iovlen = sizeof(iovec) / sizeof(*iovec);
    msg.msg_control = msg_control;
    msg.msg_controllen = sizeof(msg_control);
    msg.msg_flags = 0;

    // add IPV6_PKTINFO control message to specify source address
    cmsg_space = 0;
    cmsg = CMSG_FIRSTHDR(&msg);
    cmsg->cmsg_level = IPPROTO_IPV6;
    cmsg->cmsg_type = IPV6_PKTINFO;
    cmsg->cmsg_len = CMSG_LEN(sizeof(*pktinfo));
    pktinfo = (struct in6_pktinfo*) CMSG_DATA(cmsg);
    pktinfo->ipi6_ifindex = 0;
    inet_pton(AF_INET6, srcaddr, &pktinfo->ipi6_addr);
    cmsg_space += CMSG_SPACE(sizeof(*pktinfo));
    msg.msg_controllen = cmsg_space;

    // send packet
    if (sendmsg(sock, &msg, 0) == -1) {
        perror("send failed");
    }

    return 0;
}

【讨论】:

  • 这似乎正是我想要的!如果您不介意,我还有一些后续问题:这是否意味着我可以完全跳过使用IP6_MULTICAST_IF 并且始终仅在发出数据报时设置它?在这种情况下,是否对内核有任何性能影响,每次发送某些内容时都必须重置路由?另一个含义是我现在可以拥有一个套接字并且总是指向不同的接口/地址,而不是拥有多个套接字?这也适用于 IPv4 吗?
猜你喜欢
  • 2012-09-22
  • 1970-01-01
  • 2012-10-26
  • 1970-01-01
  • 1970-01-01
  • 2013-12-02
  • 2011-04-21
  • 1970-01-01
  • 2011-03-05
相关资源
最近更新 更多