IPv6 组播接口选择
IPv6 multicast interface selection
setsockopt
到 select 用于 IPv4 传出流量的接口的方法是 IP_MULTICAST_IF
,它接受两个参数。来自 ip(4)
手册页:
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.
尝试对 IPv6 流量执行类似操作时,选项更改为接口索引。来自 ipv6(4)
手册页:
Set the device for outgoing multicast packets on the socket.
This is allowed only for SOCK_DGRAM
and SOCK_RAW
socket. The
argument is a pointer to an interface index (see netdevice(7
))
in an integer.
如果网络接口(例如 eth0
)分配了多个地址,会发生什么情况?是不是 IPv6 套接字接口已经消除了单独使用每个地址的可能性?
如果您的接口只有一个 link-local 地址 (fe80::/10
) 和一个公共可路由地址,则传出数据包的源地址取决于您要发送到的多播地址的范围.
IPv6 多播地址的格式为 ffxy::/16
,其中 x 是标志字段,y 是范围。如果范围是 1(接口本地)或 2(link 本地),则源地址将是 link-local 地址。如果范围是 3 或更高,源地址将是公共可路由地址。
另一方面,如果您的接口有多个公共路由地址,您需要在发送数据报时使用 sendmsg
,以便您可以使用 IPV6_PKTINFO
控件设置源地址 header.
下面是如何执行此操作的完整示例,假设您在一个接口上有 2001::1:2:3
和 2002::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;
}
setsockopt
到 select 用于 IPv4 传出流量的接口的方法是 IP_MULTICAST_IF
,它接受两个参数。来自 ip(4)
手册页:
Set the local device for a multicast socket. The argument for
setsockopt(2)
is anip_mreqn
or (since Linux 3.5)ip_mreq
structure similar toIP_ADD_MEMBERSHIP
, or anin_addr
structure.
尝试对 IPv6 流量执行类似操作时,选项更改为接口索引。来自 ipv6(4)
手册页:
Set the device for outgoing multicast packets on the socket. This is allowed only for
SOCK_DGRAM
andSOCK_RAW
socket. The argument is a pointer to an interface index (seenetdevice(7
)) in an integer.
如果网络接口(例如 eth0
)分配了多个地址,会发生什么情况?是不是 IPv6 套接字接口已经消除了单独使用每个地址的可能性?
如果您的接口只有一个 link-local 地址 (fe80::/10
) 和一个公共可路由地址,则传出数据包的源地址取决于您要发送到的多播地址的范围.
IPv6 多播地址的格式为 ffxy::/16
,其中 x 是标志字段,y 是范围。如果范围是 1(接口本地)或 2(link 本地),则源地址将是 link-local 地址。如果范围是 3 或更高,源地址将是公共可路由地址。
另一方面,如果您的接口有多个公共路由地址,您需要在发送数据报时使用 sendmsg
,以便您可以使用 IPV6_PKTINFO
控件设置源地址 header.
下面是如何执行此操作的完整示例,假设您在一个接口上有 2001::1:2:3
和 2002::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;
}