为了跟进 Steve-o 的回答,即使 System.Net.Sockets.SocketOptionName 枚举没有通过直接转换数字来定义所需的选项,仍然可以在 C# 中的 IPv6 中进行源过滤。
(SocketOptionName) 45; //MCAST_JOIN_SOURCE_GROUP
即使选项不被识别,套接字的函数SetSocketOption也会让调用转到“windows套接字”。真正的斗争变成了需要与选项一起发送的数据结构本身。
要设置源过滤,数据结构必须是这样的:group_source_req。前一个结构使用sockaddr_storage,它通常位于sockaddr_in 和sockaddr_in6 的联合中。为了复制这种行为,我们可以像这样定义相同的结构:
private unsafe struct sockaddr_storage
{
public short ss_family; //2
private fixed byte __ss_pad1[6]; //6
private Int64 __ss_align; //8
private fixed byte __ss_pad2[112]; //112
}
private unsafe struct sockaddr_in
{
public ushort sin_family; //2
public ushort sin_port; //2
public fixed byte sin_addr[4]; //4
private fixed byte sub_zero[8]; //8
}
private unsafe struct sockaddr_in6
{
public ushort sin6_family; //2
public ushort sin6_port; //2
public int sin6_flowinfo; //4
public fixed byte sin6_addr[16]; //16
public uint sin6_scope_id; //4
}
private struct group_source_req
{
public uint gr_interface; //4
//Compiler add a padding here: //4
public sockaddr_storage gr_group; //128
public sockaddr_storage gr_source; //128
}
您现在可以通过执行以下操作创建一个 sockaddr_in6:
sockaddr_in6 sockIn = new sockaddr_in6
{
sin6_family = (ushort) endPoint.AddressFamily,
sin6_port = (ushort)endPoint.Port,
sin6_scope_id = 0
};
for (int i = 0; i < endPoint.Address.GetAddressBytes().Length; i++)
{
sockIn.sin6_addr[i] = endPoint.Address.GetAddressBytes()[i];
}
现在可以使用here 提供的解决方案提取 sockaddr_in6 的字节,并直接复制到之前创建的 sockaddr_storage 中:
sockaddr_storage sock = new sockaddr_storage
{
ss_family = (short)endPoint.AddressFamily
};
//[...]
byte[] sockInData = getBytes(sockIn);
byte* sockData = (byte*) &sock;
for (int i = 0; i < sockInData.Length; i++)
{
sockData [i] = sockInData[i];
}
现在您有了一个 sockaddr_storage,您可以将它分配给 group_source_req 并像我们之前所做的那样提取 group_source_req 的数据,并在您设置选项时将其用作值。
socket.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName) 45, /*data extracted from group_source_req*/);